I am trying to access data from a public Google Sheet. The sheet has read-only access to anyone. I am using official Node.js client for this. I was using a service account to authenticate the request, as I am using the same service account for accessing another sheet which can't be made public.
The code was working fine, but as soon as I updated the Node.js client to the latest version, it started giving me strange errors. I have created a minified example for the error and here's the code for this -
/*eslint-disable no-console */
const { promisify } = require('util');
const GoogleAuth = require('google-auth-library');
const { google } = require('googleapis');
const googleAuth = new GoogleAuth();
const sheets = google.sheets('v4');
sheets.spreadsheets.values.getAsync = promisify(sheets.spreadsheets.values.get);
async function authorizeWithServiceAccount(serviceAccountKey, scopes) {
try {
let authClient = await authorize(serviceAccountKey, scopes);
return authClient;
} catch (err) {
console.error(err);
throw err;
}
}
function authorize(credentials, scopes) {
return new Promise((resolve, reject) => {
googleAuth.fromJSON(credentials, (err, client) => {
if (err) {
console.error(err);
reject(err);
return;
}
client.scopes = scopes;
client.authorize((err, result) => {
if (err) {
console.error(err);
reject(err);
return;
}
console.log(result, true);
resolve(client);
});
});
});
}
async function getData(auth, spreadsheetId, range) {
try {
return sheets.spreadsheets.values.getAsync({
auth: auth,
spreadsheetId: spreadsheetId,
range: range
});
} catch (e) {
console.error(e);
throw e;
}
}
const serviceAccountJson = require('../configs/keys/service_account'); //The service account json key
const spreadsheetId = 'SPREADSHEET_ID'; // Id of the sheet I am trying to access
const apiKey = 'THE_API_KEY'; //Any API key generated on Google's API console
const range = 'A:M';
async function init() {
let authClient = await authorizeWithServiceAccount(serviceAccountJson, [
'https://www.googleapis.com/auth/spreadsheets.readonly'
]);
return getData(authClient, spreadsheetId, range); //This doesn't work and throw error
// return getData(apiKey, spreadsheetId, range); //This does work and return all the data.
}
init()
.then(result => {
console.log('Received Data');
console.log(result.data);
})
.catch(e => console.error(e));
So if I use API key instead of the service account as auth parameter, I get proper data as expected. But as soon as I use a service account instead, result.data
becomes undefined
and then I get this error.
TypeError: callback is not a function
at JWT.OAuth2Client.postRequest (/Volumes/Projects/Work/node_modules/google-auth-library/lib/auth/oauth2client.js:341:9)
at postRequestCb (/Volumes/Projects/Work/node_modules/google-auth-library/lib/auth/oauth2client.js:297:23)
at Request._callback (/Volumes/Projects/Work/node_modules/google-auth-library/lib/transporters.js:113:17)
at Request.self.callback (/Volumes/Projects/Work/node_modules/request/request.js:186:22)
at emitTwo (events.js:126:13)
at Request.emit (events.js:214:7)
at Request.<anonymous> (/Volumes/Projects/Work/node_modules/request/request.js:1163:10)
at emitOne (events.js:116:13)
at Request.emit (events.js:211:7)
at IncomingMessage.<anonymous> (/Volumes/Projects/Work/node_modules/request/request.js:1085:12)
I was using googleapis library version 25.x before and at that time service account auth was working, but as soon as I updated it to 28.x, it stopped working.
Is there any way I can use service account instead of API key in the 28.x googleapis node.js client? I can't downgrade it since I am using other Google APIs which require the latest version.
OK, I looked again at the documentation and they have mentioned it at one place about how to do it. I was previously using the google-auth library like this -
const GoogleAuth = require('google-auth-library');
const googleAuth = new GoogleAuth();
which was mentioned in the previous documentation. I guess in the sheet API's docs, can't remember. But they are now supporting auth using googleapis package along with sheets API. Thus all I had to do was switch to using the auth. So this is how I am getting authClient now and it's working in the test.
const { google } = require('googleapis');
const authClient = await google.auth.getClient({
credentials: credentials,
scopes: scopes
});
Now, I am getting proper data with the latest googleapis package / Node.js client.
So the problem was how am I getting the authClient. The older way was seemingly not compatible with the latest client.