Write Form Data from my Chrome Extension to Google Sheets - javascript

Updated with snippets and today's progress:
I am writing a Chrome Extension that is essentially a popup with a form, and I would like to write data entered into that form into Google Sheets. Currently, my extension consists of a manifest.json and a popup script, and a background script.
manifest.json (relevant pieces):
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [{ "js": ["content.js"], "matches": ["<all_urls>"] }],
"permissions": [
"tabs",
"storage",
"<all_urls>",
"identity",
"https://*.googleapis.com/*"
]
popup.js (note: this is an extension to track MS symptoms)
const app = {
symptoms: [],
init: function () {
//cache some element references
let formEl = document.getElementById("symptoms-form");
let fatigue = document.getElementById("fatigue");
let tingling = document.getElementById("tingling");
let weakness = document.getElementById("weakness");
let vision = document.getElementById("vision");
let dizzy = document.getElementById("dizzy");
let cognition = document.getElementById("cognition");
let depression = document.getElementById("depression");
let balance = document.getElementById("balance");
//upon submit, update symptoms obj and send to background
formEl.addEventListener("submit", ev => {
ev.preventDefault();
console.log('button click')
this.symptoms.push({fatigue: fatigue.value})
this.symptoms.push({tingling: tingling.value})
this.symptoms.push({weakness: weakness.value})
this.symptoms.push({vision: vision.value})
this.symptoms.push({dizzy: dizzy.value})
this.symptoms.push({cognition: cognition.value})
this.symptoms.push({depression: depression.value})
this.symptoms.push({balance: balance.value})
// chrome.runtime.sendMessage({fn: 'getSymptoms'}, function(response) {
// console.log('popup got response', response)
// })
chrome.runtime.sendMessage({fn: 'setSymptoms', symptoms: this.symptoms})
});
}
}
document.addEventListener('DOMContentLoaded', () => {
app.init();
})
background.js - note: my current workaround is to load the data into Firebase, which you will see below:
console.log("Background running");
const background = {
symptoms: [],
init: function() {
//listen for any messages and route them to functions
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.fn in background) {
background[request.fn](request, sender, sendResponse);
}
const jsonObj = {}
jsonObj['symptoms'] = request.symptoms
console.log("message received", jsonObj);
this.postSymptoms(jsonObj)
});
},
postSymptoms: function(msg) {
const xhr = new XMLHttpRequest();
xhr.open("POST", "https://ms-mysymptoms-1541705437963.firebaseio.com/symptoms.json", true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send(msg);
}
};
background.init();
I have set up a new project in the Google Developers console, enabled the Google Sheets API, and set up my credentials and API token. I tested in the Google API explorer that the authentication is set up properly and I can, indeed, write a row to my sheet. This is great news!
I am blocked right now on how to do this (write the data), directly from my Chrome extension. So far, I have saved all my credentials, set up a config file, and wrote my append method in a separate file locally.
sheets.js:
const {authorize, google} = require('./config')
const fs = require('fs')
const spreadsheetId = '---removed for this post--'
const append = (range, values) => {
fs.readFile('client_secret.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Sheets API.
authorize(JSON.parse(content), (auth) => {
const sheets = google.sheets({
version: 'v4',
auth
});
const valueInputOption = 'USER_ENTERED';
const resource = {
values
};
sheets.spreadsheets.values.append({
spreadsheetId,
range,
valueInputOption,
resource
}, (err, result) => {
if (err) {
console.log(err);
} else {
console.log("Success!");
}
});
});
});
}
// module.exports = {
// append
// };
When I try to integrate this code into my popup script, however, I encounter an error because in order to reference that config data and that append method, I have to use require in my popup script. Since the popup script is running in the browser, I can't use require (without webpack, that is).
I'm sure I'm going about this all wrong, so I could use a push in the right direction as to how to authenticate and append to Sheets from the browser if my configuration and authentication are stored in local files on my computer.
Solutions I've considered:
1 - spin up a REST API, post the data from the form to that endpoint, and have it act as a proxy to the Google Sheets API - this is not ideal.
2 - use webpack so that I can use require in my popup file
What would be the recommended way to do this? How should I integrate authentication and working with the Google Sheet into this extension?

Writing to a spreadsheet with Google's API is a PUT not a POST.
https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update
I had success with this using chrome.identity.getAuthToken, then running a fetch with the following:
chrome.identity.getAuthToken({interactive: true}, function(token) {
var params = {
'values': [
['Row 1 Col A','Row 1 Col B'],
['Row 2 Col A','Row 2 Col B'],
]
};
let init = {
method: 'PUT',
async: true,
body: JSON.stringify(params),
headers: {
Authorization: 'Bearer ' + token,
Content-Type': 'application/json'
},
contentType: 'json',
};
fetch('https://sheets.googleapis.com/v4/spreadsheets/***YOUR SHEET ID****/values/****YOUR RANGE*****?valueInputOption=USER_ENTERED&key=***YOUR API KEY***', init)
.then((response) => response.json())
.then(function(data) {
//console.log(data);
//Returns spreadsheet ID, update tange, cols and rows
});
})
});
That's all in the background script, where I've put Row 1 Col A etc as the values, that'll be the first cell of your range.
Hope that helps.

Careful! If you want to append data, the ? query parameter comes after the :append.
fetch(`https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${range}:append?valueInputOption=${valueInputOption}`, init)

Related

Download a ReadableStream from a Service Worker ? (chrome extension MV3) [duplicate]

I have a logging mechanism in place that saves the logs into an array. And I need a way to download the logs into a file.
I had this previously working (on manifest v2) with
const url = URL.createObjectURL(new Blob(reallyLongString, { type: 'text/plain' }));
const filename = 'logs.txt';
chrome.downloads.download({url, filename});
Now I am migrating to manifest v3 and since manifest v3 does not have URL.createObjectURL, you cannot create a url to pass to chrome.downloads.download
Instead it is possible to create a Blob URL using something like
const url = `data:text/plain,${reallyLongString}`;
const filename = 'logs.txt';
chrome.downloads.download({url, filename});
The problem is that chrome.downloads.download seems to have a limit on the number of characters passed in the url argument, and the downloaded file only contains a small part of the string.
So what would be a way to overcome this limitation?
Hopefully, a way to download Blob directly in service worker will be implemented in https://crbug.com/1224027.
Workaround via an extension page
Here's the algorithm:
Use an already opened page such as popup or options
Otherwise, inject an iframe into any page that we have access to
Otherwise, open a new minimized window
async function downloadBlob(blob, name, destroyBlob = true) {
// When `destroyBlob` parameter is true, the blob is transferred instantly,
// but it's unusable in SW afterwards, which is fine as we made it only to download
const send = async (dst, close) => {
dst.postMessage({blob, name, close}, destroyBlob ? [await blob.arrayBuffer()] : []);
};
// try an existing page/frame
const [client] = await self.clients.matchAll({type: 'window'});
if (client) return send(client);
const WAR = chrome.runtime.getManifest().web_accessible_resources;
const tab = WAR?.some(r => r.resources?.includes('downloader.html'))
&& (await chrome.tabs.query({url: '*://*/*'})).find(t => t.url);
if (tab) {
chrome.scripting.executeScript({
target: {tabId: tab.id},
func: () => {
const iframe = document.createElement('iframe');
iframe.src = chrome.runtime.getURL('downloader.html');
iframe.style.cssText = 'display:none!important';
document.body.appendChild(iframe);
}
});
} else {
chrome.windows.create({url: 'downloader.html', state: 'minimized'});
}
self.addEventListener('message', function onMsg(e) {
if (e.data === 'sendBlob') {
self.removeEventListener('message', onMsg);
send(e.source, !tab);
}
});
}
downloader.html:
<script src=downloader.js></script>
downloader.js, popup.js, options.js, and other scripts for extension pages (not content scripts):
navigator.serviceWorker.ready.then(swr => swr.active.postMessage('sendBlob'));
navigator.serviceWorker.onmessage = async e => {
if (e.data.blob) {
await chrome.downloads.download({
url: URL.createObjectURL(e.data.blob),
filename: e.data.name,
});
}
if (e.data.close) {
window.close();
}
}
manifest.json:
"web_accessible_resources": [{
"matches": ["<all_urls>"],
"resources": ["downloader.html"],
"use_dynamic_url": true
}]
Warning! Since "use_dynamic_url": true is not yet implemented don't add web_accessible_resources if you don't want to make your extension detectable by web pages.
Workaround via Offscreen document
Soon there'll be another workaround: chrome.offscreen.createDocument instead of chrome.windows.create to start an invisible DOM page where we can call URL.createObjectURL, pass the result back to SW that will use it for chrome.downloads.download.

Binance API Signature with Google Scripts WORKING FIRST BUT NOW NOT [duplicate]

I'm using the binance API to get the prices of usdt. The API works on postman but it doesn't work on google script.
function fetchCryptoPricesFromApi() {
const data = {
"page": 1,
"rows": 10,
"payTypes": [],
"asset": "USDT",
"tradeType": "SELL",
"fiat": "LKR",
"publisherType": null,
"transAmount": "2600"
}
const payload = JSON.stringify(data)
const options = {
"method" : "POST",
"contentType" : "application/json",
"payload" : payload
}
let response;
try {
response = UrlFetchApp.fetch('https://p2p.binance.com/bapi/c2c/v2/friendly/c2c/adv/search', options);
} catch (error) {
console.log('Oops Error, ', error);
return
}
const prices = JSON.parse(response)['data'];
console.log(prices)
}
I get the following error when executing this,
Oops Error, { [Exception: Request failed for https://p2p.binance.com returned code 403. Truncated server response: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" ... (use muteHttpExceptions option to examine full response)] name: 'Exception' }
I tried waiting some time as well.
From your showing error message, I confirmed the status code 403. Ref In this case, it is considered that the site cannot be directly accessed from the Google side. I think that the reason for your issue is due to this.
In this case, as a workaround, I would like to propose access to the URL without directly running the script with the script editor. When I tested this workaround, I confirmed that the value could be returned.
In this workaround, the following flow is used.
Put the custom function of =fetchCryptoPricesFromApi() to a cell.
Retrieve the values from the API.
Retrieve the values from the cell.
Parse the value as JSON data.
The sample script of this workaround is as follows.
Sample script:
In this workaround, I use Google Spreadsheet. So please create a new Google Spreadsheet and open the script editor of Google Spreadsheet. And, copy and paste the following script. And, run main() function with the script editor.
function fetchCryptoPricesFromApi() {
const data = {
"page": 1,
"rows": 10,
"payTypes": [],
"asset": "USDT",
"tradeType": "SELL",
"fiat": "LKR",
"publisherType": null,
"transAmount": "2600"
}
const payload = JSON.stringify(data)
const options = {
"method": "POST",
"contentType": "application/json",
"payload": payload
}
const response = UrlFetchApp.fetch('https://p2p.binance.com/bapi/c2c/v2/friendly/c2c/adv/search', options);
return response.getContentText();
}
// Please run this function.
function main() {
const sheet = SpreadsheetApp.getActiveSheet();
const range = sheet.getRange("A1");
range.setFormula("=fetchCryptoPricesFromApi()");
SpreadsheetApp.flush();
const value = range.getValue();
range.clearContent();
const prices = JSON.parse(value)['data'];
console.log(prices)
}

Chrome extension background service worker script download file from blob [duplicate]

I have a logging mechanism in place that saves the logs into an array. And I need a way to download the logs into a file.
I had this previously working (on manifest v2) with
const url = URL.createObjectURL(new Blob(reallyLongString, { type: 'text/plain' }));
const filename = 'logs.txt';
chrome.downloads.download({url, filename});
Now I am migrating to manifest v3 and since manifest v3 does not have URL.createObjectURL, you cannot create a url to pass to chrome.downloads.download
Instead it is possible to create a Blob URL using something like
const url = `data:text/plain,${reallyLongString}`;
const filename = 'logs.txt';
chrome.downloads.download({url, filename});
The problem is that chrome.downloads.download seems to have a limit on the number of characters passed in the url argument, and the downloaded file only contains a small part of the string.
So what would be a way to overcome this limitation?
Hopefully, a way to download Blob directly in service worker will be implemented in https://crbug.com/1224027.
Workaround via an extension page
Here's the algorithm:
Use an already opened page such as popup or options
Otherwise, inject an iframe into any page that we have access to
Otherwise, open a new minimized window
async function downloadBlob(blob, name, destroyBlob = true) {
// When `destroyBlob` parameter is true, the blob is transferred instantly,
// but it's unusable in SW afterwards, which is fine as we made it only to download
const send = async (dst, close) => {
dst.postMessage({blob, name, close}, destroyBlob ? [await blob.arrayBuffer()] : []);
};
// try an existing page/frame
const [client] = await self.clients.matchAll({type: 'window'});
if (client) return send(client);
const WAR = chrome.runtime.getManifest().web_accessible_resources;
const tab = WAR?.some(r => r.resources?.includes('downloader.html'))
&& (await chrome.tabs.query({url: '*://*/*'})).find(t => t.url);
if (tab) {
chrome.scripting.executeScript({
target: {tabId: tab.id},
func: () => {
const iframe = document.createElement('iframe');
iframe.src = chrome.runtime.getURL('downloader.html');
iframe.style.cssText = 'display:none!important';
document.body.appendChild(iframe);
}
});
} else {
chrome.windows.create({url: 'downloader.html', state: 'minimized'});
}
self.addEventListener('message', function onMsg(e) {
if (e.data === 'sendBlob') {
self.removeEventListener('message', onMsg);
send(e.source, !tab);
}
});
}
downloader.html:
<script src=downloader.js></script>
downloader.js, popup.js, options.js, and other scripts for extension pages (not content scripts):
navigator.serviceWorker.ready.then(swr => swr.active.postMessage('sendBlob'));
navigator.serviceWorker.onmessage = async e => {
if (e.data.blob) {
await chrome.downloads.download({
url: URL.createObjectURL(e.data.blob),
filename: e.data.name,
});
}
if (e.data.close) {
window.close();
}
}
manifest.json:
"web_accessible_resources": [{
"matches": ["<all_urls>"],
"resources": ["downloader.html"],
"use_dynamic_url": true
}]
Warning! Since "use_dynamic_url": true is not yet implemented don't add web_accessible_resources if you don't want to make your extension detectable by web pages.
Workaround via Offscreen document
Soon there'll be another workaround: chrome.offscreen.createDocument instead of chrome.windows.create to start an invisible DOM page where we can call URL.createObjectURL, pass the result back to SW that will use it for chrome.downloads.download.

OAuth for GAPI - Avoid Authentication and Authorization after initial sign in for Javascript

I have created a chrome extension that reads email, does something and create tasks using google client API for javascript.
I am using chrome identity for authentication and authorization.
The extension works as expected. However, it keeps asking for sign every once in a while. What I want is to authorize the user in the background script so that they don't need to do it over and over again, after the initial authentication and authorization.
What I have done so far:
I read that I need a refresh token to avoid this. However, refresh tokens are expected to be exchanged and stored on the server side and not client side (which wouldn't work because the background script is doing the job here which is client side)
Using gapi.auth.authorize with immediate true. That gives error regarding external visibility. When I read else, they suggested using it inside a server. I am not sure how can I do that in a chrome extension.
Turn interactive to false in getAuthToken, which starts giving error 401 due to authentication problem after the access token expires.
Following is the code I am using for authentication and authorization, with function onGoogleLibraryLoaded being called after loading the google api's client js file.
var signin = function (callback) {
chrome.identity.getAuthToken({interactive: true}, callback);
};
function onGoogleLibraryLoaded() {
signin(authorizationCallback);
}
var authorizationCallback = function (data) {
gapi.auth.setToken({access_token: data});
gapi.client.load('tasks', 'v1')
gapi.client.load('gmail', 'v1', function () {
console.log("Doing my stuff after this ..")
});
};
UPDATE:
As per the suggestion in the answer, I made some changes to the code. However, I am still facing the same issue. Following is the updated code snippet
jQuery.loadScript = function (url, callback) {
jQuery.ajax({
url: url,
dataType: 'script',
success: callback,
async: false
});
}
//This is the first thing that happens. i.e. loading the gapi client
if (typeof someObject == 'undefined') $.loadScript('https://apis.google.com/js/client.js',
function(){
console.log("gapi script loaded...")
});
//Every 20 seconds this function runs with internally loads the tasks and gmail
// Once the gmail module is loaded it calls the function getLatestHistoryId()
setInterval(function() {
gapi.client.load('tasks', 'v1')
gapi.client.load('gmail', 'v1', function(){
getLatestHistoryId()
})
// your code goes here...
}, 20 * 1000); // 60 * 1000 milsec
// This is the function that will get user's profile and when the response is received
// it'll check for the error i.e. error 401 through method checkForError
function getLatestHistoryId(){
prevEmailData = []
var request = gapi.client.gmail.users.getProfile({
'userId': 'me'
});
request.execute(function(response){
console.log("User profile response...")
console.log(response)
if(checkForError(response)){
return
}
})
}
// Now here I check for the 401 error. If there's a 401 error
// It will call the signin method to get the token again.
// Before calling signin it'll remove the saved token from cache through removeCachedAuthToken
// I have also tried doing it without the removeCachedAuthToken part. However the results were the same.
// I have left console statements which are self-explanatory
function checkForError(response){
if("code" in response && (response["code"] == 401)){
console.log(" 401 found will do the authentication again ...")
oooAccessToken = localStorage.getItem("oooAccessTokenTG")
console.log("access token ...")
console.log(oooAccessToken)
alert("401 Found Going to sign in ...")
if(oooAccessToken){
chrome.identity.removeCachedAuthToken({token: oooAccessToken}, function(){
console.log("Removed access token")
signin()
})
}
else{
console.log("No access token found to be remove ...")
signin()
}
return true
}
else{
console.log("Returning false from check error")
return false
}
}
// So finally when there is 401 it returns back here and calls
// getAuthToken with interactive true
// What happens here is that everytime this function is called
// there is a signin popup i.e. the one that asks you to select the account and allow permissions
// That's what is bothering me.
// I have also created a test chrome extension and uploaded it to chrome web store.
// I'll share the link for it separately.
var signin = function (callback) {
console.log(" In sign in ...")
chrome.identity.getAuthToken({interactive: true}, function(data){
console.log("getting access token without interactive ...")
console.log(data)
gapi.auth.setToken({access_token: data});
localStorage.setItem("oooAccessTokenTG", data)
getLatestHistoryId()
})
};
Manifest goes like this:
{
"manifest_version": 2,
"name": "Sign in Test Extension ",
"description": "",
"version": "0.0.0.8",
"icons": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
},
"content_security_policy": "script-src 'self' 'unsafe-eval' https://apis.google.com; object-src 'self'",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"identity",
"storage"
],
"oauth2": {
"client_id": "1234.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/gmail.readonly"
]
},
"background":{
"scripts" : ["dependencies/jquery.min.js", "background.js"]
}
}
Anyone else facing the same issue?
I am also using the identity API for google authorization in my chrome extension. I used to get the 401 status when my google token expired. So I added a check that if I am getting 401 status response of my request, then I will again authorize and get the token (it will happen in background) and continue my work.
Here is an example from my background.js
var authorizeWithGoogle = function() {
return new Promise(function(resolve, reject) {
chrome.identity.getAuthToken({ 'interactive': true }, function(result) {
if (chrome.runtime.lastError) {
alert(chrome.runtime.lastError.message);
return;
}
if (result) {
chrome.storage.local.set({'token': result}, function() {
resolve("success");
});
} else {
reject("error");
}
});
});
}
function getEmail(emailId) {
if (chrome.runtime.lastError) {
alert(chrome.runtime.lastError.message);
return;
}
chrome.storage.local.get(["token"], function(data){
var url = 'https://www.googleapis.com/gmail/v1/users/me/messages/id?alt=json&access_token=' + data.token;
url = url.replace("id", emailId);
doGoogleRequest('GET', url, true).then(result => {
if (200 === result.status) {
//Do whatever from the result
} else if (401 === result.status) {
/*If the status is 401, this means that request is unauthorized (token expired in this case). Therefore refresh the token and get the email*/
refreshTokenAndGetEmail(emailId);
}
});
});
}
function refreshTokenAndGetEmail(emailId) {
authorizeWithGoogle().then(getEmail(emailId));
}
I don't need to log in again and again manually. The google token is refreshed automatically in the background.
So this is what I believe would be the answer to my question.
Few important things to know
Chrome sign in is not same as gmail sign in. You could have UserA signed into chrome, while you plan to use the chrome extension with UserB. chrome.identity.getAuthToken won't work in that case, because it looking for the user signed into chrome.
For using other google accounts i.e. the one not signed into chrome, you would need to use chrome.identity.launchWebAuthFlow. Following are the steps you can use. I am referring the example given here (Is it possible to get an Id token with Chrome App Indentity Api?)
Go to google console, create your own project > Credentials > Create Credentials > OAuthClientID > Web Application. On that page in the field Authorized redirect URIs, enter the redirect url in the format https://.chromiumapp.org. If you don't know what chrome extension ID is, refer this (Chrome extension id - how to find it)
This would generate a client id that would go into your manifest file. Forget about any previous client id you might have created. Let's say in our example the client id is 9999.apps.googleusercontent.com
Manifest file:
{
"manifest_version": 2,
"name": "Test gmail extension 1",
"description": "description",
"version": "0.0.0.1",
"content_security_policy": "script-src 'self' 'unsafe-eval' https://apis.google.com; object-src 'self'",
"background": {
"scripts": ["dependencies/jquery.min.js", "background.js"]
},
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"identity",
"storage"
],
"oauth2": {
"client_id": "9999.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/tasks"
]
}
}
Sample code for getting user's info in background.js
jQuery.loadScript = function (url, callback) {
jQuery.ajax({
url: url,
dataType: 'script',
success: callback,
async: false
});
}
// This is the first thing that happens. i.e. loading the gapi client
if (typeof someObject == 'undefined') $.loadScript('https://apis.google.com/js/client.js',
function(){
console.log("gapi script loaded...")
});
// Every xx seconds this function runs with internally loads the tasks and gmail
// Once the gmail module is loaded it calls the function getLatestHistoryId()
setInterval(function() {
gapi.client.load('tasks', 'v1')
gapi.client.load('gmail', 'v1', function(){
getLatestHistoryId()
})
// your code goes here...
}, 10 * 1000); // xx * 1000 milsec
// This is the function that will get user's profile and when the response is received
// it'll check for the error i.e. error 401 through method checkForError
// If there is no error i.e. the response is received successfully
// It'll save the user's email address in localstorage, which would later be used as a hint
function getLatestHistoryId(){
var request = gapi.client.gmail.users.getProfile({
'userId': 'me'
});
request.execute(function(response){
console.log("User profile response...")
console.log(response)
if(checkForError(response)){
return
}
userEmail = response["emailAddress"]
localStorage.setItem("oooEmailAddress", userEmail);
})
}
// Now here check for the 401 error. If there's a 401 error
// It will call the signin method to get the token again.
// Before calling the signinWebFlow it will check if there is any email address
// stored in the localstorage. If yes, it would be used as a login hint.
// This would avoid creation of sign in popup in case if you use multiple gmail accounts i.e. login hint tells oauth which account's token are you exactly looking for
// The interaction popup would only come the first time the user uses your chrome app/extension
// I have left console statements which are self-explanatory
// Refer the documentation on https://developers.google.com/identity/protocols/OAuth2UserAgent >
// Obtaining OAuth 2.0 access tokens > OAUTH 2.0 ENDPOINTS for details regarding the param options
function checkForError(response){
if("code" in response && (response["code"] == 401)){
console.log(" 401 found will do the authentication again ...")
// Reading the data from the manifest file ...
var manifest = chrome.runtime.getManifest();
var clientId = encodeURIComponent(manifest.oauth2.client_id);
var scopes = encodeURIComponent(manifest.oauth2.scopes.join(' '));
var redirectUri = encodeURIComponent('https://' + chrome.runtime.id + '.chromiumapp.org');
// response_type should be token for access token
var url = 'https://accounts.google.com/o/oauth2/v2/auth' +
'?client_id=' + clientId +
'&response_type=token' +
'&redirect_uri=' + redirectUri +
'&scope=' + scopes
userEmail = localStorage.getItem("oooEmailAddress")
if(userEmail){
url += '&login_hint=' + userEmail
}
signinWebFlow(url)
return true
}
else{
console.log("Returning false from check error")
return false
}
}
// Once you get 401 this would be called
// This would get the access token for user.
// and than call the method getLatestHistoryId again
async function signinWebFlow(url){
console.log("THE URL ...")
console.log(url)
await chrome.identity.launchWebAuthFlow(
{
'url': url,
'interactive':true
},
function(redirectedTo) {
if (chrome.runtime.lastError) {
// Example: Authorization page could not be loaded.
console.log(chrome.runtime.lastError.message);
}
else {
var response = redirectedTo.split('#', 2)[1];
console.log(response);
access_token = getJsonFromUrl(response)["access_token"]
console.log(access_token)
gapi.auth.setToken({access_token: access_token});
getLatestHistoryId()
}
}
);
}
// This is to parse the get response
// referred from https://stackoverflow.com/questions/8486099/how-do-i-parse-a-url-query-parameters-in-javascript
function getJsonFromUrl(query) {
// var query = location.search.substr(1);
var result = {};
query.split("&").forEach(function(part) {
var item = part.split("=");
result[item[0]] = decodeURIComponent(item[1]);
});
return result;
}
Feel free to get in touch with me if you have any questions. I have spent quite a few days joining these dots. I wouldn't want someone else to do the same.

Automate Google Cloud Print from Drive Folder

To preface, I have Google Cloud Print working through apps script. I have OAuth2 setup, and I was able to setup a Cloud Print API that prints a single file in my Google Drive to a printer on my Cloud Print.
With that said, I'm looking for a way to automate my script so that when a document gets placed in a specific folder on my Google Drive, it will print automatically. I've searched around and was unable to find anything similar. Here's my starting point (which was found here from a very helpful tutorial):
function printGoogleDocument(docId, docTitle) {
// For notes on ticket options see https://developers.google.com/cloud-print/docs/cdd?hl=en
var ticket = {
version: "1.0",
print: {
color: {
type: "STANDARD_COLOR"
},
duplex: {
type: "NO_DUPLEX"
},
}
};
var payload = {
"printerid": myPrinterId,
"content": docId,
"title": docTitle,
"contentType": "google.kix", // allows you to print google docs
"ticket": JSON.stringify(ticket),
};
var response = UrlFetchApp.fetch('https://www.google.com/cloudprint/submit', {
method: "POST",
payload: payload,
headers: {
Authorization: 'Bearer ' + getCloudPrintService().getAccessToken()
},
"muteHttpExceptions": true
});
// If successful, should show a job here: https://www.google.com/cloudprint/#jobs
response = JSON.parse(response);
if (response.success) {
Logger.log("%s", response.message);
} else {
Logger.log("Error Code: %s %s", response.errorCode, response.message);
}
return response;
}
So when I fill in my docID and PrinterID, it works fine for a single document. But like I said, I'm trying to automate this based on new files in a Drive folder. Any suggestions?

Categories

Resources