Skip to content

Instantly share code, notes, and snippets.

@moorage
Created August 28, 2023 21:14
Show Gist options
  • Save moorage/6f599f4dc85ae52c505f5e304c326935 to your computer and use it in GitHub Desktop.
Save moorage/6f599f4dc85ae52c505f5e304c326935 to your computer and use it in GitHub Desktop.
How to fetch gmail in batch with javascript / typescript
const gmailBatchEndpoint = 'https://www.googleapis.com/batch/gmail/v1'
const batchLimit = 2
async function fetchFullMessages(
auth: OAuth2Client,
messageIds: string[] = []
) {
const messageQueries = messageIds.map(function (id) {
return { uri: '/gmail/v1/users/me/messages/' + id }
})
const limitedMessageQueries = messageQueries.slice(0, batchLimit)
return await fetchBatch(
(
await auth.getAccessToken()
).token!,
limitedMessageQueries,
'batch_taskjet_google_lib_api',
gmailBatchEndpoint
)
}
function fetchInboxMessageIds(
auth: OAuth2Client,
messageIds: string[] = [],
// messageIds: gmail_v1.Schema$Message[] = [],
pageToken = ''
) {
return new Promise<string[]>((resolve, reject) => {
let args = {
userId: 'me',
labelIds: ['INBOX']
}
if (pageToken) {
args = Object.assign({}, args, { pageToken })
}
const g = gmail({ version: 'v1', auth: auth as any })
g.users.messages
.list(args)
.then(res => {
messageIds = (messageIds || []).concat(
(res.data.messages || []).map(message => message.id!)
)
if (res.data.nextPageToken && pageToken !== res.data.nextPageToken)
return resolve(
fetchInboxMessageIds(auth, messageIds, res.data.nextPageToken)
)
return resolve(messageIds)
})
.catch(err => {
return reject(err)
})
})
}
// @see https://github.com/EmilTholin/google-api-batch-utils/blob/master/lib/index.js
import queryString from 'query-string'
function parsePart(part: string) {
var p = part.substring(part.indexOf('{'), part.lastIndexOf('}') + 1)
return JSON.parse(p)
}
/**
* Takes an array of API call objects and generates a fetch call to Google batch API.
* @param {string} accessToken - Access token for the API calls.
* @param {object[]} apiCalls
* @param {string} apiCalls[].uri - Uri of the API call.
* @param {string} apiCalls[].[method] - Optional HTTP method. Defaults to GET.
* @param {object} apiCalls[].[qs] - Optional object with querystring parameters.
* @param {string} apiCalls[].[body] - Optional request body string.
* @param {string} [boundary] - Optional String that delimits the calls.
* @param {string} [batchUrl] - Optional URL to the batch endpoint.
*
* @return {string}
*/
export async function fetchBatch(
accessToken: string,
apiCalls: {
uri: string
method?: string | null | undefined
qs?: any | null | undefined
body?: string | null | undefined
}[],
boundary: string = 'batch_taskjet_google_lib_api',
batchUrl: string = 'https://www.googleapis.com/batch'
) {
const createBatchBody = function (
apiCalls: {
uri: string
method?: string | null | undefined
qs?: any | null | undefined
body?: string | null | undefined
}[],
boundary: string
) {
var batchBody: string[] = []
apiCalls.forEach(function (call) {
var method = call.method || 'GET'
var uri = call.uri
if (call.qs) {
uri += '?' + queryString.stringify(call.qs)
}
var body = '\r\n'
if (call.body) {
body = [
'Content-Type: application/json',
'\r\n\r\n',
JSON.stringify(call.body),
'\r\n'
].join('')
}
batchBody = batchBody.concat([
'--',
boundary,
'\r\n',
'Content-Type: application/http',
'\r\n\r\n',
method,
' ',
uri,
'\r\n',
body
])
})
return batchBody.concat(['--', boundary, '--']).join('')
}
var batchBody = createBatchBody(apiCalls, boundary)
const response = await fetch(batchUrl, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + accessToken,
'Content-Type': 'multipart/mixed; boundary="' + boundary + '"'
},
body: batchBody
})
const text = await response.text()
console.log('batchBody', batchBody, 'accessToken', accessToken, 'text', text)
return parseBatchResponse(text)
}
/**
* Parses a raw string response from the Google batch API into objects.
* @param {string} response
* @return {object[]}
*/
function parseBatchResponse(response: string): any[] {
// Not the same delimiter in the response as was specified in the request,
// so we have to extract it.
var delimiter = response.substring(0, response.indexOf('\r\n'))
var parts = response.split(delimiter)
// The first part will always be an empty string. Just remove it.
parts.shift()
// The last part will be the "--". Just remove it.
parts.pop()
var result = []
for (var i = 0; i < parts.length; i++) {
var part = parts[i]
try {
result.push(parsePart(part))
} catch (e) {
// A Google API error will contain a JSON response with an array 'errors'.
// If the parsing should fail, we mimic this.
result.push({
errors: [{ message: part, response: response, parts: parts, error: e }]
})
}
}
return result
}
@moorage
Copy link
Author

moorage commented Aug 28, 2023

Usage:

//   const inboxMessages = await Promise.all(
//     oauths.map(async c => {
//       const client = new OAuth2Client(
//         process.env.GOOGLE_ID,
//         process.env.GOOGLE_SECRET
//       )
//       client.setCredentials({
//         access_token: c.access_token,
//         refresh_token: c.refresh_token,
//         expiry_date: new Date(c.expires_at).getTime(),
//         token_type: 'Bearer'
//       })
//       return fetchFullMessages(client, await fetchInboxMessageIds(client))
//     })
//   )

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