Chrome Extension Service Worker inactive after "Clear site data" - google-chrome

I have a very simple background script in my manifest v3 extension that basically waits for messages and then retrieves a value from sync storage like so:
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
chrome.storage.sync.get("value", ({ value }) => {
... do some stuff
}
}
Now this works fine until i goto the DevTools of my extension -> Application -> Storage -> Clear site data.
After that my service worker is always "inactive" and does not become active when I send messages to it. Only when I go to chrome://extensions and disable/enable the extension the service worker starts functioning again.
The weirdest thing is that my popup.js can actually still get the value from sync storage. How is that possible after I clear the site data?

Related

GoogleFit.authorize not responding/working

In my one of React Native app, there is a feature to connect with Google Fit. When user tapps on connects Google Fit, it prompts for device accounts to choose the account and then nothing happens, no error event receives also. I can't understand whats the issue.
Although this is working fine in debug and release build. When we upload the app to Google Play, and user downloads the app from play store, then only problem occurs.
Can anyone please help me?
const options = {
scopes: [
Scopes.FITNESS_ACTIVITY_READ,
Scopes.FITNESS_BODY_READ,
Scopes.FITNESS_HEART_RATE_READ,
Scopes.FITNESS_BLOOD_PRESSURE_READ,
Scopes.FITNESS_SLEEP_READ,
],
}
GoogleFit.authorize(options)
.then(async authResult => {
console.log(JSON.stringify(authResult));
if (authResult.success) {
dispatch(actionCreator.getActionObject('UPDATE_isGooglefitLoggedIn', true));
}
})
.catch((error) => {
alert("AUTH_ERROR", JSON.stringify(error));
})
Given the users cannot signin using their Google account, make sure you configured the right SHA release certificate in the GCP project. When you release your App in Play Store, the signature of the binary signed by the upload key certificate will be different than the binary distributed by Google Play. You can find the Play App Signing page under Release > Setup > App Integrity and update your GCP OAuth client accordingly.

Is there a way to stop web-push notification on a Google chrome on one device if it is already received on a Google chrome on another device?

I have implemented push notifications using web-push that works on Google chrome.
Is there a way to stop web-push notification on a Google chrome on one device if it is already received on a Google chrome on another device?
This would require you to access something in a distributed store before pushing the notification to the end user. For example, your logic could be something similar to:
// Only one client can get a lock at a time
var lock = await api.getPushNotificationDistributedLock();
// Get the status
var pushNotificationStatus = await api.getPushNotificationStatus();
// Send the notification if it hasn't already been sent
if(pushNotificationStatus === "pending") {
// Push notification to user
await api.setPushNotificationStatus("completed");
}
// Release the lock
lock.release();
The first client to get the lock will see the status as pending and successfully send the push notification. After it updates the status to completed, every subsequent client will see the status as completed and carry on.

Is it possible to watch Directory API changes from Google App Maker/Google Apps Script?

I'm working on an performance evaluation app in Google App Maker. One of the challenges we have with our current tool is that it doesn't sync with our G Suite directory when a person's manager changes or when a person has a name change -- their existing evaluations are linked to the person's old name and we have to change manually.
In my new app, I have an Employees datasource that includes a relation to the evaluation itself that was initially populated via the Directory API. Reading the documentation here, it seems as though I should be able to set up a watch on the Users resource to look for user updates and parse through them to make the appropriate name and manager changes in my Employees datasource. What I can't figure out, though, is what the receiving URL should be for the watch request.
If anyone has done this successfully within Google App Maker, or even solely within a Google Apps Script, I'd love to know how you did it.
EDITED TO ADD:
I created a silly little GAS test function to see if I can get #dimu-designs solution below to work. Unfortunately, I just get a Bad Request error. Here's what I have:
function setUserWatch() {
var optionalArgs = {
"event": "update"
};
var resource = {
"id": "10ff4786-4363-4681-abc8-28166022425b",
"type": "web_hook",
"address": "https://script.google.com/a/.../...hXlw/exec"
};
AdminDirectory.Users.watch(resource);
}
Address is the current web app URL.
EDITED TO ADD MORE:
The (in)ability to use GAS to receive web hooks has been an active issue/feature request since Sep 2014 -- https://issuetracker.google.com/issues/36761910 -- which has been #dimu-designs on top of for some time.
This is a more comprehensive answer.
Google supports push notifications across many of their APIs. However there are many subtle (and not so subtle) differences between them. Some that leverage webhooks send their data payloads primarily as HTTP headers; for example Drive API and Calendar API. Others mix their payloads across HTTP headers and a POST body(ex: AdminDirectory API). And its gets even crazier, with some APIs utilizing different mechanisms altogether (ex: GMail API leverages Cloud PubSub).
There are nuances to each but your goal is to leverage AdminDirectory push notifications in a GAS app. To do that you need a GAS Web App whose URL can serve as a web-hook endpoint.
STEP 1 - Deploy A Stand-Alone Script As A Web App
Let's start with the following template script and deploy it as a Web App from the Apps Script Editor menu Publish > Deploy As Web App:
/** HTTP GET request handler */
function doGet(e) {
return ContentService.createTextOutput("GET message");
}
/** HTTP POST request handler */
function doPost(e) {
return ContentService.createTextOutput("POST message");
}
STEP 2 - Verify/Validate Domain Ownership And Add/Register Domain
NOTE: As of August 2019, GAS Web App URLs can no longer be verified using this method. Google Cloud Functions may be a viable
alternative.
With the web app deployed you now have to verify and register the domain of the receiving url, which in this case is also the web app url. This url takes the following form:
https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/exec
Technically you cannot own a GAS web app url's domain. Thankfully the App Script Gods at Google do provide a mechanism to verify and register a GAS web app url.
From the Apps Script Editor menu select Publish > Register in Chrome Web Store. Registering a published web app with the Chrome Web Store also validates the URL's domain (no need to fiddle with the search console).
Once validated you need to add the "domain" via the Domain verification page in the API Console. The "domain" is everything in the url sans the 'exec', so you'll add a string that looks like this:
https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/
STEP 3 - Make a watch request
For this step the AdminSDK/Directory API service should be enabled both for your App Script project and in the API Console.
Create a function that generates a watch request (this can be retooled for other event types):
function startUpdateWatch() {
var channel = AdminDirectory.newChannel(),
receivingURL = "https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/exec",
gSuiteDomain = "[business-name].com",
event = "update";
channel.id = Utilities.getUuid();
channel.type = "web_hook";
channel.address = receivingURL + "?domain=" + gSuiteDomain + "&event=" + event;
channel.expiration = Date.now() + 21600000; // max of 6 hours in the future; Note: watch must be renew before expiration to keep sending notifications
AdminDirectory.Users.watch(
channel,
{
"domain":gSuiteDomain,
"event":event
}
);
}
Note that Directory API push notifications have an expiration, the max being 6 hours from starting the watch so it must be renewed periodically to ensure notifications are sent to the endpoint URL. Typically you can use a time-based trigger to call this function every 5 hours or so.
STEP 4 - Update doPost(e) trigger to handle incoming notifications
Unlike the push mechanisms of other APIs, the Directory API sends a POST body along with its notifications, so the doPost(e) method is guaranteed to be triggered when a notification is sent. Tailor the doPost(e) trigger to handle incoming events and re-deploy the web app:
function doPost(e) {
switch(e.parameter.event) {
case "update":
// do update stuff
break;
case "add":
break;
case "delete":
break;
}
return ContentService.createTextOutput("POST message");
}
There is one caveat to keep in mind. Push notifications for update events only tell you that the user's data was updated, it won't tell you exactly what was changed. But that's a problem for another question.
Note that there are a ton of details I left out but this should be enough to get you up and running.
You can do this with GAS and the Admin SDK. The Directory API supports Notifications (Note this is scheduled to be deprecated so not sure what is replacing this functionality). You can then set up a GMAIL script to do what you need to do with the notification.
UPDATE: There are also PUSH notifications from the Directory API.
I was able to set up push notifications for a local resource (a spreadsheet) using Heroku-based Node.js app as an intermediary API. The Node app captures the custom request headers and builds the payload to be consumed by the doPost(e) function of the GAS web app.
The code for constructing a watch request is simple
//get the unique id
var channelId = Utilities.getUuid();
//build the resource object
var resource = {
"id": channelId,
"type": "web_hook",
"address": "https://yourapp.herokuapp.com/drivesub
}
//watch the resource
Drive.Files.watch(resource, fileId);
The challenge is to get that domain address verified. There are ways to verify the standalone (not file-bound!) GAS web app, however, as previous posters have mentioned, the Apps Script web app can't access custom headers.
After you've enabled the Pub/Sub API and created the topic & subscription, go to APIs & Services -> Credentials -> Domain verification. It gives you a few options of verifying your domain, including serving the html file. Download the file generated by Google. Thankfully, Heroku makes it very easy to deploy a Node app
https://devcenter.heroku.com/articles/getting-started-with-nodejs
After your domain is verified you can make your subscription push data to the endpoint URL on Heroku.
I simply created the js file for route handlers and created one specifically for domain verification
handlers.verifyDomain = function(callback){
//Synchronously read from the static html file. Async method fs.readFile() doesn't make the file available to the router callback
var file = fs.readFileSync('./google/google_file.html');
callback(200, file);
}
Then include the handler in your router object like so:
var router = {
"google_file.html": handlers.verifyDomain
}
Finally, in your server starting function, get the path from the URL (there are multiple ways of doing that), and execute the handler;
var routeHandler = router(path);
routerHandler(function(statusCode, file){
//do something
});
Go back to domain verification tool and load the HTML page to verify the URL. After it's verified, the only remaining step is creating the GAS web app and posting to it.
Back to Node app. Note that my endpoint is https://yourapp.herokuapp.com/drivesub
//The code for posting data to GAS web app. Of course, you need to
// update your router with router['driveSub'] = handlers.driveSub
handlers.driveSub = function(data, callback){
var headers = data.headers;
var options = {
method:"POST",
uri: your_gas_app_url,
json:{"headers":headers}
};
//Use NPM to install the request module
request.post(options, function(err, httpResponse, body){
console.log(err, body);
});
callback(200, data);
}
Apps Script app - don't forget to publish it.
function doPost(req) {
var postData = req.postData["contents"];
var headers = JSON.parse(postData)["headers"];
//take action
return 200;
}
Unfortunately you cannot, at least not solely using Apps Script.
Admin Directory push notifications require a web-hook URL endpoint to receive notifications. You might think deploying a GAS web app and using its URL as an endpoint would be sufficient. But the thing with Admin Directory Push notifications is that its data payload resides in custom HTTP headers which cannot be accessed from a GAS Web App. (This also holds true for push notifications across other APIs including the Drive and Calendar APIs)
You can however leverage Google Cloud Functions (a GCP service) in tandem with GAS, but you'll have to know your way around Node.js.
EDIT
After giving this some thought, and reviewing your requirements I believe there is a way to pull this off just using GAS.
You can setup a unique push notification channel for a given event per user/domain (the 'update' event in your use case) by setting the event parameter when initializing the watch. Thus the GAS web app will only be triggered if an update event occurs; you don't really need to rely on the HTTP header to determine the event type.
If you want to track multiple events, you simply create a unique channel per event and use the same GAS Web app endpoint for each one. You differentiate between events by checking the event parameter sent in the POST request. This should remove the need for middle-man services such as Heroku or Google Cloud Functions.

gapi.auth.authorize() with immediate: false doesn't popup a window for authorization

Below is a javascript which simply requests authorization to access user's spreadsheets:
var CLIENT_ID = '********';
var SCOPES = [
'https://www.googleapis.com/auth/spreadsheets'
];
function auth() {
gapi.auth.authorize({
client_id: CLIENT_ID,
immediate: true,
scope: SCOPES
}, function(result) {
console.log('authorize(immediate = true)');
if (result && !result.error) {
console.log('authorize [OK]');
} else {
console.log('authorize [FAILED]');
gapi.auth.authorize({
client_id: CLIENT_ID,
immediate: false,
scope: SCOPES
}, function(result) {
console.log('authorize(immediate = false)');
if (result && !result.error) {
console.log('authorize [OK]');
} else {
console.log('authorize [FAILED]');
}
});
}
});
}
I believe it should do two things:
Popup a window to login unless the user is already logged in.
Popup a window to request authorization to access user's spreadsheets unless the authorization had been already granted earlier. After the authorization is granted the app should be listed under Connected apps & sites and no more popup with authorization will be shown.
I am testing this script with two distinct google accounts. One account works as expected and I get the following output on console:
auth.html:17 authorize(immediate = true)
auth.html:21 authorize [FAILED]
auth.html:27 authorize(immediate = false)
auth.html:29 authorize [OK]
With another account the popup for authorization is not shown and authorization is always granted as If I pressed "Allow" or the app was listed under Connected apps & sites, but it's not there. The console output is exactly the same.
I have done these tests using two browsers:
Version 51.0.2704.79 Built on 8.4, running on Debian 8.5 (64-bit)
Firefox ESR 45.2.0, running on Debian 8.5 (64-bit)
So, basically I have the following questions:
Are my expectations regarding popups correct or the idea behind the gapi.auth.authorize() call with immediate:true or immediate:false is different?
What can be the reason for this "misbehaving"? Is there any "sacred place" where the app is listed as authorized for some scope while the same app not shown under Connected apps & sites?
Note: The CLIENT_ID is listed in Google API Console under OAuth
2.0 client IDs, the type is Web application and the owner is completely different account not related to the above mentioned two.
Thanks.
To answer your questions:
Are my expectations regarding popups correct or the idea behind the gapi.auth.authorize() call with immediate:true or immediate:false is different?
Yes, your expectations regarding popups are correct. As discussed in a related issue #103 posted in GitHub, when user triggers 'gapi.auth.authorize' with button click (immediate:false), flow is as following:
Popup window with permissions authorization is shown
When user accept/deny, popup window is closed
instead of firing callback, TypeError appears in console (regardless of whether user authorized app to handle requested data or not)
What can be the reason for this "misbehaving"? Is there any "sacred place" where the app is listed as authorized for some scope while the same app not shown under Connected apps & sites?
That related issue #103 posted in GitHub can also be the reason for this "misbehaving" and based from the thread, this has already been fixed which can be found in that GitHub post.
I hope that helps.

Google Chrome GCM registration doesn't work

I have problem with GCM registration in Google Chrome. I've created minimal example:
manifest.json
{
"name": "TestGCM",
"version": "0.1",
"description": "Desc.",
"permissions": ["gcm"],
"background": {
"scripts": ["background.js"],
"persistent": true
},
"manifest_version": 2
}
background.js
var gcmSenderId = "782709818071";
var registerGcm = function(gcmRegistrationId) {
console.log("registerGcm start");
var lastError = chrome.runtime.lastError;
if (lastError) {
console.error("Error during registering GCM token: ", lastError.message);
}
console.log("registerGcm end");
};
var senderIds = [gcmSenderId];
console.log("registering GCM...");
chrome.gcm.register(senderIds, registerGcm);
console.log("...");
var lastError = chrome.runtime.lastError;
if (lastError) {
console.error("Error: ", lastError.message);
}
After I load this extension I only see:
registering GCM...
...
Registration doesn't work. I tried resetting Google Chrome settings to factory, reinstalling Chrome, restarting PC.
In chrome://gcm-internals/ I see that GCM client state is UNINITIALIZED:
Android Id
User Profile Service Created true
GCM Enabled true
GCM Client Created true
GCM Client State UNINITIALIZED
Connection Client Created false
Connection State
Registered App Ids
Send Message Queue Size 0
Resend Message Queue Size 0
There is a chance that you are dealing with corrupted GCM store. If this is a signed-in (see below) profile, and GCM Client is still shown as UNINITIALIZED, that would be the case. A way to deal with that is to go a corresponding profile's folder and delete the GCM Store folder. Restarting Chrome should reinitialize GCM for the signed in profile, and your application should be able to work.
My only concern about that solution is that you mentioned that you reinstalled Chrome, which I would expect to delete the folders and this solution would not address the problem. On an off chance that you are using the old profile and it happens to be corrupted, please try these steps.
GCM Client in desktop Chrome is built in a way, that you don't need to be signed-in to have GCM running, it is enough to have an app/extension using GCM, but signing in and enabling Chrome Sync is an easy way to test that GCM works properly. That is the only reason I am recommending it.
Check this documentation about Implementing GCM Client on Chrome, to know the basic steps you need to obtain GCM registration token.
It's explained here the steps and key points that you need to register the GCM. It has a sample code that you can use or serve as a guide for your project.
Also make sure that the enable API that you use is "Google Cloud Messaging for Android". Because "Google Cloud Messaging for Android" will give you access to the normal GCM API. "Google Cloud Messaging for Chrome" won’t (it’s used for Chrome Apps in the Chrome Web Store).
Also check this tutorial on how to use Google Cloud Messaging in Chrome.