I've had a long-running Chrome Extension where I'm used to always have access to the background.js page for viewing console, trouble-shooting etc.
I'm implementing Firebase login and have made some changes...and now, after I log into my app, the 'background page' becomes the html of the current popup.
Manifest below...
When you reload or first install the Extension I see what I'm used to...and can click on the link to view the "_generated_background_page.html". Buttons in the popup correctly communicate (via messaging) to run functions in background.js.
However, after logging in, the new popup (I redirect to a new popup for logged in users) replaces the background page (my words) and (most importantly) I can't access the background page anymore. Messaging doesn't have any effect and I can't "see" the console / inspect the background.js page.
In the past I've seen a background page AND another, open page and can inspect them both.
Any thoughts on how I have succeeded in painting myself into a corner? It's as if I'm closing the background.js file.
Manifest:
{
"manifest_version": 2,
"name": "Annotate PRO for Chrome",
"short_name": "Annotate PRO",
"description": "Right-click access to a pre-written library of comments. Write it once, to perfection, and reuse forever!",
"version": "3.1.1.0",
"permissions": [
"identity",
"identity.email",
"clipboardWrite",
"clipboardRead",
"activeTab",
"tabs",
"contextMenus",
"storage",
"webNavigation"
],
"content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'",
"externally_connectable": {
"matches": ["http://*.11trees.com/*"]},
"commands": {
"_execute_browser_action": {
"suggested_key": {
"windows": "Alt+A",
"mac": "Alt+A",
"chromeos": "Alt+A",
"linux": "Alt+A"
}
}
},
"key": "XXX",
"oauth2": {
/*"client_id": "XXX",*/
"client_id": "XXX",
"scopes": [
/*"https://www.googleapis.com/auth/chromewebstore.readonly",*/
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile"
]
},
"background": {
"scripts": ["/dscripts/jquery-3.1.1.min.js","/dscripts/firebase.js","/scripts/background.js"]},
"content_security_policy": "script-src 'self' https://ssl.google-analytics.com https://apis.google.com/ https://www.gstatic.com/ https://*.firebaseio.com https://www.googleapis.com; object-src 'self'",
"content_scripts": [
{
"all_frames" : true,
"matches": ["http://*/*","https://*/*"],
"js": ["/scripts/content.js"]
}
],
"icons": {
"16": "Annotate16.png",
"48": "Annotate48.png",
"128": "Annotate128.png"
},
"browser_action": {
"default_icon": {
"19": "Annotate128.png",
"38": "Annotate128.png"
},
"default_title": "Annotate PRO for Google Chrome",
"default_popup": "aHome.html"
}
}
background.js
//URLs for scripts
var baseURL = "http://localhost/AnnotateX/Scripts/Dev/"; //local server - macOS
var xmlhttp = new XMLHttpRequest(); //why put this up front? We need a new object on each call...
//Firebase constants
var config = {
apiKey: "XXX",
authDomain: "XXX",
databaseURL: "XXX",
storageBucket: "XXX",
// messagingSenderId: "XXX"
};
firebase.initializeApp(config);
//listener for chrome start
chrome.runtime.onStartup.addListener(initApp()); //This fires verification check...
function initApp() {
// Listen for auth state changes.
// [START authstatelistener]
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
var displayName = user.displayName;
var email = user.email;
var emailVerified = user.emailVerified;
// var photoURL = user.photoURL;
// var isAnonymous = user.isAnonymous;
var uid = user.uid;
var providerData = user.providerData;
console.log('We\'re a user...coming through: ' + providerData);
if (user.emailVerified) { //Account is verified
console.log('We\'re a VERIFIED user... ' + emailVerified);
var url1 = baseURL + "aCheckUsers.php"
var url2 = "&fbUserID=" + uid + "&UserEmail=" + email + "&fullName=" + displayName;
$.ajax({
type: "POST",
url: url1,
data: url2,
dataType: 'json',
success: function(arrResult){
arrUserData = arrResult;
console.log('User data: ') + console.log(arrUserData);
localStorage.userDetails = JSON.stringify(arrUserData);
localStorage.userID = arrUserData.userID;
localStorage.licType = arrUserData.LicType;
startup();
},
error: function (jqXHR, textStatus, errorThrown) {
console.log('Error: ' + errorThrown + ' / ' + textStatus) + console.log(jqXHR);
}
});
}
// [START_EXCLUDE]
// [END_EXCLUDE]
} else {
// Let's try to get a Google auth token programmatically.
// [START_EXCLUDE]
console.log('Not a user (background.js)');
// [END_EXCLUDE]
}
});
}
function signOut() {
console.log("Logging out via subroutine in background.js.");
firebase.auth().signOut();
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {method: "logOut"});
});
chrome.browserAction.setPopup({ //Sets popup to last visited
popup: 'aHome.html' // Open this html file within the popup.
});
}
//function that determines whether userID exists and library can be loaded or if new user must be created first
function startup(){
console.log("Starting up...");
chrome.storage.sync.get('lastSave', function(obj) {
var syncSaveTime = obj.lastSave;
var localSaveTime = localStorage.lastSync;
console.log('local: ' + localSaveTime + ' | sync: ' + syncSaveTime);
// if (localSaveTime == null || syncSaveTime >= localSaveTime){ //test
if (localSaveTime == null || syncSaveTime > localSaveTime){ //production
// console.log("Current user: " + localStorage.userID);
console.log("Local version is outdated...should run db pulll...");
pullLibrary();
} //End process for running library load if outdated or NO data locally...
else {
console.log("We've got data - skip the heavyweight pull....use local");
processLibrary(JSON.parse(localStorage.library));
}
}); //End async storage GET
} //End STARTUP
// Firebase auth popups
function googleLoginPopUp() {
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
// This gives you a Google Access Token. You can use it to access the Google API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
console.log(errorCode + ' - ' + errorMessage);
// ...
});
} //End Google Login
function facebookLoginPopUp() {
var provider = new firebase.auth.FacebookAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
// This gives you a Facebook Access Token. You can use it to access the Facebook API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorCode + ' - ' + errorMessage);
});
}
Okay...good night's sleep and some basic debugging...
Part of my background.js startup process was to hand the user off to the post-login popup page.
self.location.href='aSearch.html'; //finally open core search page
I don't understand exactly why, but this line in background.js effectively replaced background.js with the aSearch.html and aSearch.js pages...background.js became unavailable to messaging etc.
Removing the line did the trick...and you can't open a page from background.js anyway.
Related
I got this sample code from the docs of actions on google account linking with google account. The signin.status is always "ERROR". I have tried on actions console simulator, google assistant app on my phone and on a google home mini with personal results on. But the result is the same in all cases.
const express = require('express');
const bodyParser = require('body-parser');
const {actionssdk, SignIn} = require('actions-on-google');
const app = actionssdk({
// REPLACE THE PLACEHOLDER WITH THE CLIENT_ID OF YOUR ACTIONS PROJECT
clientId: <client_id>,
});
// Intent that starts the account linking flow.
app.intent('actions.intent.MAIN', (conv) => {
conv.ask(new SignIn('To get your account details'));
});
// Create an Actions SDK intent with the `actions_intent_SIGN_IN` event.
app.intent('actions.intent.SIGN_IN', (conv, params, signin) => {
console.log(signin)
if (signin.status === 'OK') {
const payload = conv.user.profile.payload;
conv.ask(`I got your account details, ${payload.name}. What do you want to do next?`);
} else {
conv.ask(`I won't be able to save your data, but what do you want to do next?`);
}
});
app.intent('actions.intent.TEXT', (conv) => {
conv.close("bye");
})
//Run server
const expressApp = express().use(bodyParser.json());
expressApp.post('/', function(req,res){
app(req,res);
});
expressApp.listen(8080,() => {console.log("listening")});
This is the signin object I'm being returned
{ '#type': 'type.googleapis.com/google.actions.v2.SignInValue',
status: 'ERROR' }
EDIT
My actions.json is as follows
{
"actions": [
{
"description": "Default Welcome Intent",
"name": "MAIN",
"fulfillment": {
"conversationName": "fulfilment function"
},
"intent": {
"name": "actions.intent.MAIN",
"trigger": {
"queryPatterns": [
"talk to Care Cat"
]
}
}
},
{
"description": "Everything Else Intent",
"name": "allElse",
"fulfillment": {
"conversationName": "fulfilment function"
},
"intent": {
"name": "actions.intent.TEXT"
}
}
],
"conversations": {
"fulfilment function": {
"name": "fulfilment function",
"url": <url>
}
},
"locale": "en"
}
Could it be because it is still a test app which is not published yet?
Can someone help me with this?
In your Google Cloud Platform Account, check your IAM settings and enable the Dialogflow API Admin
Documentation for more details: https://cloud.google.com/dialogflow/docs/access-control
I've been trying to integrate FCM with a chrome extension I am making.
In my service worker file(firebase-messaginng-sw.js), I am getting the following error:
FirebaseError: Messaging: This method is available in a service worker
context. (messaging/only-available-in-sw).
Below are the relevant code snippets.
manifest.json
"permissions": ["tabs", "storage", "notifications"],
"background": {
"scripts": ["./lib/firebase/firebase-app.js",
"./lib/firebase/firebase-auth.js",
"./lib/firebase/firebase-messaging.js",
"./background.js"
]
},
background.js
const messaging = firebase.messaging();
messaging.usePublicVapidKey("XXXXXX");
const currentToken = await messaging.getToken();
console.log(`Current token ${currentToken}`);
if ('serviceWorker' in navigator) {
await navigator.serviceWorker.register('./firebase-messaginng-sw.js');
initialiseState();
} else {
console.warn('Service workers aren\'t supported in this browser.');
}
firebase-messaginng-sw.js
firebase.initializeApp({
'messagingSenderId': '111111111'
});
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function(payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
const notificationTitle = 'Background Message Title';
const notificationOptions = {
body: 'Background Message body.',
icon: './public/icon.png'
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
The whole integration part has been quite difficult for me as many resources online are deprecated or don't work for extensions. If you can also guide me towards any guide specifically for this, that would be amazing.
Here's what I've done to create a Chrome extension that receives notifications via Firebase and it's working for me. Some of this might be redundant or unnecessary or whatever. But it seems to work.
Note that I never call firebase-messaging-sw.js directly. Firebase apparently loads it itself.
manifest.json
{
"version": "1.1.7",
"name": "Push Test",
"permissions": [
"notifications",
"https://www.gstatic.com/",
"https://www.gstatic.com/firebasejs/7.6.1/firebase-app.js"
],
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"manifest_version": 2,
"description": "This extension is just a push-notification tester.",
"icons": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
},
"content_security_policy": "script-src 'self' https://www.gstatic.com; object-src 'self'"
}
popup.html
<!doctype html>
<html>
<head>
<title>Push Test</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
</head>
<body>
<p id="pushLabel">This is just a test of push notifications via Firebase.</p>
<script src="jquery-1.10.2.min.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.6.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.6.1/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.6.1/firebase-messaging.js"></script>
<script src="popup.js"></script>
</body>
</html>
popup.js
Notification.requestPermission().then((permission) => {
if (permission === 'granted')
{
console.log('Notification permission granted.');
// TODO(developer): Retrieve an Instance ID token for use with FCM.
// ...
}
else
{
console.log('Unable to get permission to notify.');
}
});
// Your web app's Firebase configuration
var firebaseConfig = {
/*
What you should put here can be found inside
https://console.firebase.google.com/
*/
};
console.log('firebase:');
console.log(firebase);
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
console.log('firebase initialized');
const messaging = firebase.messaging();
console.log('messaging created');
messaging.usePublicVapidKey("PutYourOwnKeyHere");
console.log('key defined');
setTimeout(function()
{
console.log('timeout running');
// Get Instance ID token. Initially this makes a network call, once retrieved
// subsequent calls to getToken will return from cache.
messaging.getToken().then((currentToken) => {
console.log('getToken');
if (currentToken)
{
console.log('getToken success');
$('#pushLabel').text(currentToken);
}
else
{
console.log('getToken failure');
// Show permission request.
$('#pushLabel').text('No Instance ID token available. Request permission to generate one.');
}
}).catch((err) => {
console.log('getToken error: ' + err);
});
// Callback fired if Instance ID token is updated.
messaging.onTokenRefresh(() => {
console.log('onTokenRefresh');
messaging.getToken().then((refreshedToken) => {
$('#pushLabel').text(currentToken);
}).catch((err) => {
console.log('onTokenRefresh error: ' + err);
});
});
}, 5000);
firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/7.6.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/7.6.1/firebase-auth.js');
importScripts('https://www.gstatic.com/firebasejs/7.6.1/firebase-messaging.js');
// Your web app's Firebase configuration
var firebaseConfig = {
/*
What you should put here can be found inside
https://console.firebase.google.com/
*/
};
console.log('firebase:');
console.log(firebase);
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
console.log('firebase initialized');
// Retrieve Firebase Messaging object.
const messaging = firebase.messaging();
content.js This is the content page for chrome extension
document.getElementById("signIn").addEventListener("click", function(){
chrome.runtime.sendMessage({task:"switchUser", user: current_user},function(response){
});
});
background.js This is the background page for chrome extension
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse){
if(request.task == "switchUser"){
function getToken(){
chrome.identity.getAuthToken({ interactive: true }, function(token) {
sendResponse(token);
});
}
chrome.identity.removeCachedAuthToken({ token:
currentSessionAccessToken }, getToken);
}
return true;
});
Previous OAuth token is successfully removed but when generating a new one using getAuthToken, the user selection list is not shown. However, I have set interactive to true. What am I missing?
You need to revoke the token first and then remove from cache. Please find the code below for background.js page.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse){
if(request.task == "switchUser"){
function getToken(){
chrome.identity.getAuthToken({ interactive: true }, function(token) {
sendResponse(token);
});
}
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('GET', 'https://accounts.google.com/o/oauth2/revoke?token=' + currentSessionAccessToken); //revoke the token calling this url.
xmlHttp.onload = function() {
chrome.identity.removeCachedAuthToken({ token: currentSessionAccessToken }, getToken); //after token is revoked, remove it from cache as well.
}
xmlHttp.onerror = function(){
};
xmlHttp.send();
}
return true;
});
I'm developing a chrome extension which will add Authorization headers to the requests.
For this I used onBeforeSendHeaders, but unfortunately it isn't working for FTP requests. I have given the permission in manifest and also in background
Manifest permissions says:
"permissions": [
"webRequest",
"webRequestBlocking",
"webNavigation",
"tabs",
"cookies",
"ftp://*/*",
"*://*/*"
]
and I used onBeforeSendHeaders like this:
chrome.webRequest.onBeforeSendHeaders.addListener(
function(details) {
if(details.url == "my_url"){
details.requestHeaders.push({'name':'Authorization','value':'my_value'});
return { requestHeaders: details.requestHeaders };
}
},
{urls: ['<all_urls>','ftp://*/*']},
[ 'blocking', 'requestHeaders']
);
callback(true);
}
And also if I fail to open FTP in Chrome; is there any other way, where I could open FTP in terminal or putty using a single command (something like ssh user#host -pw pass)?
onAuthRequired is the solution to it, and worked for me:
var target = "ftp://ftpurl/";
var myCredentials = {
username: "username",
password: "pass"
};
var pendingRequests = [];
// A request has completed.
// We can stop worrying about it.
function completed(requestDetails) {
console.log("completed: " + requestDetails.requestId);
var index = pendingRequests.indexOf(requestDetails.requestId);
if (index > -1) {
pendingRequests.splice(index, 1);
}
}
function provideCredentialsSync(requestDetails) {
// If we have seen this request before, then
// assume our credentials were bad, and give up.
if (pendingRequests.indexOf(requestDetails.requestId) != -1) {
console.log("bad credentials for: " + requestDetails.requestId);
return {cancel:true};
}
pendingRequests.push(requestDetails.requestId);
console.log("providing credentials for: " + requestDetails.requestId);
return {authCredentials: myCredentials};
}
chrome.webRequest.onAuthRequired.addListener(
provideCredentialsSync,
{urls: [target]},
["blocking"]
);
chrome.webRequest.onCompleted.addListener(
completed,
{urls: [target]}
);
chrome.webRequest.onErrorOccurred.addListener(
completed,
{urls: [target]}
);
}
I'm using a chrome extension with a button in the popup.html that opens a new tab. The destination URL of the new tab holds the URL of the current (original) tab as a parameter.
For instance: when fired from http://stackoverflow.com/, the new tab should have an URL like http://www.mydestination.com/index.php?url=http://stackoverflow.com/
Here's my js:
document.addEventListener('DOMContentLoaded', function (tab) {
document.getElementById('button').addEventListener("click", function(tab) {
chrome.tabs.create({url: 'http://www.mydestination.com/index.php?url=' + tab.url});
});
})
The new tab is opened perfectly, but the URL is http://www.mydestination.com/index.php?url=undefined (url = undefined).
I reckon the manifest.json holds the right permissions:
{
"manifest_version": 2,
"name": "My project",
"version" : "1.7",
"browser_action": {
"default_icon" : "img/icon.png",
"default_title" : "My project",
"default_popup": "html/main.html"
},
"permissions": [
"tabs"
],
"icons": {
"16": "img/icon.png"
}
}
Any clues on how to get the url transported properly?
The problem is that current tab is your chrome popup. In this case you don't have valid URL.
You have to select your tab. To do this you can use chrome.tabs.query. Select current window with active tab:
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('button').addEventListener("click", function () {
chrome.tabs.query({
'active': true,
'windowId': chrome.windows.WINDOW_ID_CURRENT
}, function (tabs) {
chrome.tabs.create({
url: 'http://www.mydestination.com/index.php?url=' + tabs[0].url
});
});
});
});
The problem is that you are passing tab as a parameter when it has nothing to do with the events. While some chrome.* apis do include a tab object as a parameter, you can't just add it on like that and expect it to have the info you want. You could do something like this:
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('button').addEventListener("click", function() {
chrome.tabs.query({active:true, currentWindow:true},function(tab){
// Just doing it like this to make it fit on the page
var newUrl = "http://www.mydestination.com/index.php?url=" + tab[0].url;
chrome.tabs.create({url:newUrl});
});
});
});