Skip to content

Instantly share code, notes, and snippets.

@OctaneInteractive
Created April 6, 2023 09:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OctaneInteractive/8a96cb6bc5a48bb338548d78762e8a68 to your computer and use it in GitHub Desktop.
Save OctaneInteractive/8a96cb6bc5a48bb338548d78762e8a68 to your computer and use it in GitHub Desktop.
Methods to interact with various Google API endpoints
const {
google
} = require('googleapis')
const stream = require('stream')
const configurationsForApplication = require('../../common/configurations')
const RedisFactory = require('../../modules/redis')
const clientForRedis = RedisFactory(configurationsForApplication.databases.Redis)
/**
* Export an object containing methods for various Google APIs.
* @param {String} typeOfAction The type of action performed by the User, used in the selection of the appropriate redirect URL.
*/
module.exports = ({
typeOfAction
}) => {
const Google = {}
/**
* Authentication.
*/
Google.createAuthorizationUrl = ({
scopes,
state
}) => {
return new Promise(resolve => {
const oAuth2Client = new google.auth.OAuth2(process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_ID, process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_SECRET, getRedirectUrl(typeOfAction))
const authorizationUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
response_type: 'code',
prompt: 'consent',
redirect_uri: getRedirectUrl(typeOfAction),
scope: JSON.parse(scopes).join(' '),
state: state,
include_granted_scopes: true
})
return resolve({
authorizationUrl: authorizationUrl
})
})
.catch(error => {
return new Error(error)
})
}
/**
* Create an authorized OAuth2 client from an existing Access Token [and Refresh Token, if appropriate].
* @param {String} userId ID of the User.
* @return Object OAuth2 object.
*/
Google.createAuthClient = ({
userId
}) => {
return new Promise(resolve => {
Google.getAccessTokenForGoogleAccount({
userId
})
.then(oAuth2Client => resolve(oAuth2Client))
})
.catch(error => {
return new Error(error)
})
}
Google.connectViaGoogleAccount = ({
codeOfAuthentication
}) => {
return new Promise((resolve, reject) => {
const oAuth2Client = new google.auth.OAuth2(process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_ID, process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_SECRET, getRedirectUrl(typeOfAction))
oAuth2Client.getToken(codeOfAuthentication, async (error, tokens) => {
if (error) return reject(new Error(error))
await oAuth2Client.setCredentials(tokens)
const user = await Google.getPersonFromGooglePeople(oAuth2Client)
return resolve({
...user,
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token
})
})
})
.catch(error => {
return new Error(error)
})
}
Google.connectViaGoogleDrive = ({
codeOfAuthentication
}) => {
return new Promise(resolve => {
const oAuth2Client = new google.auth.OAuth2(process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_ID, process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_SECRET, getRedirectUrl(typeOfAction))
oAuth2Client.getToken(codeOfAuthentication, (error, tokens) => {
if (error) return reject(new Error(error))
oAuth2Client.setCredentials(tokens)
return resolve({
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token
})
})
})
.catch(error => {
return new Error(error)
})
}
Google.connectViaGoogleGmail = ({
codeOfAuthentication
}) => {
return new Promise(resolve => {
const oAuth2Client = new google.auth.OAuth2(process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_ID, process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_SECRET, getRedirectUrl(typeOfAction))
oAuth2Client.getToken(codeOfAuthentication, (error, tokens) => {
if (error) return reject(new Error(error))
oAuth2Client.setCredentials(tokens)
return resolve({
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token
})
})
})
.catch(error => {
return new Error(error)
})
}
/**
* Get the Access Token. In addition, the Access Token and Refresh Token are kept up to date via the Google API, and then stored on Redis.
* @param {String} userId ID of the User.
* @return Object OAuth2 object.
*/
Google.getAccessTokenForGoogleAccount = ({
userId
}) => {
return new Promise((resolve, reject) => {
const oAuth2Client = new google.auth.OAuth2(process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_ID, process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_SECRET, getRedirectUrl(typeOfAction))
clientForRedis.get(`accessTokenForGoogleAccount:${userId}`, (error, accessToken) => {
if (error) return reject(new Error(`Error with the Access Token: ${error}`))
if (accessToken) {
oAuth2Client.setCredentials({
access_token: accessToken.replace(/"/g, "")
})
return resolve(oAuth2Client)
} else {
Google.getRefreshTokenForGoogleAccount({
userId
})
.then(async refreshToken => {
await oAuth2Client.setCredentials({
refresh_token: refreshToken.replace(/"/g, "")
})
await oAuth2Client.getAccessToken((error, token) => {
if (error) return reject(new Error(error))
clientForRedis.setex(`accessTokenForGoogleAccount:${userId}`, 3600, JSON.stringify(token))
})
return resolve(oAuth2Client)
})
}
})
})
}
Google.setAccessTokenForGoogleAccount = ({
userId,
accessToken
}) => {
return new Promise(resolve => {
clientForRedis.setex(`accessTokenForGoogleAccount:${userId}`, 3600, JSON.stringify(accessToken))
return resolve({
status: true
})
})
.catch(error => {
return new Error(error)
})
}
Google.getRefreshTokenForGoogleAccount = ({
userId
}) => {
return new Promise(resolve => {
clientForRedis.get(`refreshTokenForGoogleAccount:${userId}`, (error, refreshToken) => {
if (error) return reject(new Error(`Error with the Refresh Token: ${error}`))
if (refreshToken) {
return resolve(refreshToken)
}
})
})
.catch(error => {
return new Error(error)
})
}
Google.setRefreshTokenForGoogleAccount = ({
userId,
refreshToken
}) => {
return new Promise(resolve => {
clientForRedis.setex(`refreshTokenForGoogleAccount:${userId}`, 604800, JSON.stringify(refreshToken))
return resolve({
status: true
})
})
.catch(error => {
return new Error(error)
})
}
Google.deleteAccessTokenForGoogleAccount = ({
userId
}) => {
return new Promise((resolve, reject) => {
clientForRedis.del(`accessTokenForGoogleAccount:${userId}`, error => {
if (error) {
return reject(new Error(error))
} else {
return resolve({
status: true
})
}
})
})
.catch(error => {
return new Error(error)
})
}
Google.deleteRefreshTokenForGoogleAccount = ({
userId
}) => {
return new Promise((resolve, reject) => {
clientForRedis.del(`refreshTokenForGoogleAccount:${userId}`, error => {
if (error) {
return reject(new Error(error))
} else {
return resolve({
status: true
})
}
})
})
.catch(error => {
return new Error(error)
})
}
/**
* People.
*/
/**
*
* @param {Object} OAuth2 object.
* @return Object An object containing identifying data of the person in question, via the People API.
*/
Google.getPersonFromGooglePeople = (oAuth2Client) => {
return new Promise( async resolve => {
const service = google.people({
version: 'v1',
auth: oAuth2Client
})
const {
data
} = await service.people.get({
resourceName: 'people/me',
personFields: 'names,emailAddresses'
})
const name = data.names[0]
const emailAddress = data.emailAddresses[0].value
return resolve({
firstName: name.givenName,
lastName: name.familyName,
nameOfPerson: name.displayName,
email: emailAddress
})
})
.catch(error => {
return new Error(error)
})
}
/**
* Drive.
*/
/**
* Lists the names and IDs of up to 10 files.
* @param {String} userId Used by `createAuthClient()` to create an authorized OAuth2 client.
*/
Google.getAllFilesFromDrive = ({
userId,
pageToken
}) => {
return new Promise( async resolve => {
const oAuth2Client = await Google.createAuthClient({
userId
})
const drive = google.drive({
version: 'v3',
auth: oAuth2Client
})
const response = await drive.files.list({
corpora: 'user',
pageSize: 10,
orderBy: 'createdTime desc',
// q: "name='elvis233424234'",
pageToken: pageToken ? pageToken : '',
// Fields ( https://developers.google.com/drive/api/v3/reference/files ).
fields: 'nextPageToken,files(id,name,iconLink,mimeType,size,thumbnailLink,createdTime)'
})
const filesFromDrive = response.data.files
if (filesFromDrive.length) {
const files = []
filesFromDrive.forEach(file => {
files.push({
id: file.id,
name: file.name,
iconLink: file.iconLink,
mimeType: file.mimeType,
size: file.size,
thumbnailLink: file.thumbnailLink,
createdAt: (() => {
const createdAt = new Date(file.createdTime)
return createdAt.toISOString()
})()
})
})
return await resolve(files)
}
})
.catch(error => {
return new Error(error)
})
}
Google.uploadFileToDrive = ({ userId, fileToUpload }) => {
return new Promise( async resolve => {
const oAuth2Client = await Google.createAuthClient({
userId
})
const bufferStream = new stream.PassThrough()
bufferStream.end(fileToUpload.buffer)
const drive = google.drive({
version: 'v3',
auth: oAuth2Client
})
drive.files.create({
resource: {
name: fileToUpload.originalname
},
media: {
mimeType: fileToUpload.mimetype,
body: bufferStream
},
fields: 'id,name,iconLink,mimeType,size,createdTime'
})
.then(response => {
const file = response.data
return resolve({
id: file.id,
name: file.name
})
})
})
.catch(error => {
return new Error(error)
})
}
Google.getFileFromDrive = ({ userId, fileId }) => {
return new Promise( async resolve => {
const oAuth2Client = await Google.createAuthClient({
userId
})
const drive = google.drive({
version: 'v3',
auth: oAuth2Client
})
drive.files.get({
fileId: fileId,
fields: '*'
})
.then(response => {
console.info('Google.getFileFromDrive()', response.data)
const file = response.data.file
return resolve(file)
})
})
.catch(error => {
return new Error(error)
})
}
/**
* Gmail.
*/
Google.getAllLabelsFromGmail = ({ userId }) => {
return new Promise( async resolve => {
const oAuth2Client = await Google.createAuthClient({
userId
})
const gmail = google.gmail({
version: 'v1',
auth: oAuth2Client
})
const response = await gmail.users.labels.list({
userId: 'me'
})
const labelsFromGmail = response.data.labels
if (labelsFromGmail.length) {
const labels = []
labelsFromGmail.forEach(label => {
if (label.type === 'user') {
labels.push({
id: label.id,
name: label.name
})
}
})
return resolve(labels)
}
})
.catch(error => {
return new Error(error)
})
}
/**
* Messages that have the chosen Labels assigned to them.
* @param {String} userId Used by `createAuthClient()` to create an authorized OAuth2 client.
* @param {Array} labels An array of ID values of the Labels chosen by the User.
* @return Object An object containing identifying data of the Messages that have the chosen Labels assigned to them, via the Gmail API.
*/
Google.getAllMessagesByLabelsFromGmail = ({ userId, labels, pageToken }) => {
return new Promise( async resolve => {
const arrayOfLabels = JSON.parse(labels)
const messages = []
if (arrayOfLabels.length > 0) {
const oAuth2Client = await Google.createAuthClient({
userId
})
const gmail = google.gmail({
version: 'v1',
auth: oAuth2Client
})
// List ( https://developers.google.com/gmail/api/reference/rest/v1/users.messages/list ).
const messagesAsList = await gmail.users.messages.list({
userId: 'me',
labelIds: arrayOfLabels,
maxResults: 10,
...(pageToken && {
pageToken: pageToken
})
})
if (messagesAsList.data.resultSizeEstimate > 0) {
for await (const messageAsList of messagesAsList.data.messages) {
const message = await Google.getMessagesByIdFromGmail({ userId, messageFromList: messageAsList })
messages.push(message)
}
return resolve({
resultSizeEstimate: messagesAsList.data.resultSizeEstimate,
nextPageToken: messagesAsList.data.nextPageToken,
messages
})
}
}
})
.catch(error => {
return new Error(error)
})
}
/**
* Message by ID.
* @param {String} userId Used by `createAuthClient()` to create an authorized OAuth2 client.
* @param {Array} messageFromList An object containing the ID values for the messages and their threads.
* @return Object An object of the Message.
*/
Google.getMessagesByIdFromGmail = async ({ userId, messageFromList }) => {
const oAuth2Client = await Google.createAuthClient({
userId
})
const gmail = google.gmail({
version: 'v1',
auth: oAuth2Client
})
// Get ( https://developers.google.com/gmail/api/reference/rest/v1/users.messages/get ).
const messageFromGmail = await gmail.users.messages.get({
userId: 'me',
id: messageFromList.id
})
/**
* Here, `parts[0]` is plain text, and `parts[1]` is HTML.
* Also, the `parts` array isn't guaranteed to be available.
* @param {Object} message An object containing a wealth of data.
* @return String A string representative of the body of the message.
*/
function getMessage (message) {
const messageAsText = message.data.payload.parts?.find(part => part.mimeType === 'text/plain') // Part 0.
const messageAsCode = message.data.payload.parts?.find(part => part.mimeType === 'text/html') // Part 1.
if (messageAsCode) {
return new Buffer.from(messageAsCode.body.data, 'base64').toString('utf-8')
} else if (messageAsText) {
return new Buffer.from(messageAsText.body.data, 'base64').toString('utf-8')
} else {
return message.data.snippet
}
}
const getField = fieldToFind => {
const [field] = messageFromGmail.data.payload.headers.filter(header => header.name === fieldToFind)
return field.value
}
return {
id: messageFromList.id,
threadId: messageFromList.threadId,
from: (() => {
return getField('From')
})(),
subject: (() => {
return getField('Subject')
})(),
body: await getMessage(messageFromGmail),
createdAt: (() => {
const createdAt = new Date(getField('Date'))
return createdAt.toISOString()
})()
}
}
/**
* Remove the Labels assigned to the Message.
* @param {String} userId Used by `createAuthClient()` to create an authorized OAuth2 client.
* @param {Array} labels An array of ID values of the Labels chosen by the User.
* @return {String} A string of the ID of the Message.
*/
Google.removeLabelsAssignedToMessageFromGmail = ({ userId, messageId, labels }) => {
return new Promise( async resolve => {
const arrayOfLabels = JSON.parse(labels)
const oAuth2Client = await Google.createAuthClient({
userId
})
const gmail = google.gmail({
version: 'v1',
auth: oAuth2Client
})
gmail.users.messages.modify({
userId: 'me',
id: messageId,
removeLabelIds: arrayOfLabels
})
return resolve({
id: messageId
})
})
.catch(error => {
return new Error(error)
})
}
/**
* YouTube.
*/
Google.getYouTubeVideoByID = (videoId) => {
return new Promise(resolve => {
let youtube = google.youtube({
version: 'v3',
auth: process.env.UNIQUE_NAMESPACE_GOOGLE_YOUTUBE_API_KEY
})
// YouTube Data API ( https://developers.google.com/youtube/v3/docs/videos ).
return youtube.search.list({
part: 'snippet',
q: `v=${videoId}`
})
.then(response => {
if (response.data.items.length > 0) {
let video = response.data.items[0].snippet
return resolve({
id: videoId,
title: video.title,
note: video.description,
createdAt: (() => {
const createdAt = new Date(video.publishedAt)
return createdAt.toISOString()
})()
})
}
})
})
.catch(error => {
return new Error(error)
})
}
/**
* Utilities.
*/
/**
* Get the appropriate URL based on the action performed by the User.
* @param {String} typeOfAction The type of action performed by the User.
* @return {String} URL.
*/
function getRedirectUrl (typeOfAction) {
switch (typeOfAction) {
case 'isNewUserViaGoogleAccount':
return process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_REDIRECT_URL.split(',')[1]
case 'isExistingUserViaGoogleAccount':
return process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_REDIRECT_URL.split(',')[2]
case 'authorizeGoogleDrive':
return process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_REDIRECT_URL.split(',')[3]
case 'authorizeGoogleGmail':
return process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_REDIRECT_URL.split(',')[4]
case 'isDefault':
return process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_REDIRECT_URL.split(',')[0]
}
}
return Google
}
@OctaneInteractive
Copy link
Author

It took me some time to figure out how to work with the different Google API, so I'm sharing this with anyone who's in the same situation I was a few months ago.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment