Skip to content

Commit

Permalink
Use Gmail API client instead of IMAP client when account is created w…
Browse files Browse the repository at this point in the history
…ith API scopes
  • Loading branch information
andris9 committed Feb 9, 2024
1 parent 4e6d1e4 commit abd91e8
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 65 deletions.
145 changes: 96 additions & 49 deletions lib/api-client/gmail-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ const { oauth2Apps } = require('../oauth2-apps');
const getSecret = require('../get-secret');
const msgpack = require('msgpack5')();
const logger = require('../logger');
const util = require('util');
const addressparser = require('nodemailer/lib/addressparser');
const libmime = require('libmime');
const he = require('he');

const { REDIS_PREFIX } = require('../consts');

const fs = require('fs');

const GMAIL_API_BASE = 'https://gmail.googleapis.com';
Expand Down Expand Up @@ -68,8 +69,45 @@ class GmailClient {
constructor(account, options) {
this.account = account;
this.options = options || {};

this.accountLogger = options.accountLogger;
this.redis = options.redis;
this.logger = options.logger || logger;

this.subconnections = [];
}

async init() {
// No-op
}

async delete() {
// No-op
}

getAccountKey() {
return `${REDIS_PREFIX}iad:${this.account}`;
}

getMailboxListKey() {
return `${REDIS_PREFIX}ial:${this.account}`;
}

getMailboxHashKey() {
return `${REDIS_PREFIX}iah:${this.account}`;
}

getLogKey() {
// this format ensures that the key is deleted when user is removed
return `${REDIS_PREFIX}iam:${this.account}:g`;
}

getLoggedAccountsKey() {
return `${REDIS_PREFIX}iaz:logged`;
}

currentState() {
return 'connected';
}

async getAccount() {
Expand Down Expand Up @@ -108,12 +146,13 @@ class GmailClient {
}

async listMailboxes(options) {
console.log('LIST MAILBOXES', options);
await this.prepare();

console.log(1);
const accessToken = await this.getToken();

console.log(2, accessToken);
let labelsResult = await this.oAuth2Client.request(accessToken, `${GMAIL_API_BASE}/gmail/v1/users/me/labels`);

console.log(3, labelsResult);
let labels = labelsResult.labels.filter(label => !SKIP_LABELS.includes(label.id));

let resultLabels;
Expand All @@ -127,7 +166,9 @@ class GmailClient {
resultLabels = labels;
}

resultLabels = resultLabels
console.log(3, resultLabels);

let mailboxes = resultLabels
.map(label => {
let pathParts = label.name.split('/');
let name = pathParts.pop();
Expand Down Expand Up @@ -191,8 +232,9 @@ class GmailClient {

return a.path.toLowerCase().localeCompare(b.path.toLowerCase());
});
console.log(555, mailboxes);

return { mailboxes: resultLabels };
return mailboxes;
}

getEnvelope(messageData) {
Expand Down Expand Up @@ -433,6 +475,7 @@ class GmailClient {
}

async listMessages(query) {
console.log('LIST MESSAGES', query);
await this.prepare();

const accessToken = await this.getToken();
Expand Down Expand Up @@ -711,62 +754,66 @@ class GmailClient {

module.exports = { GmailClient };

const { redis } = require('../db');
if (/gmail-client\.js$/.test(process.argv[1])) {
console.log('RUN AS STANDALONE');

let main = async () => {
let gmailClient = new GmailClient('andris', { redis });
let main = async () => {
const { redis } = require('../db');

Check failure on line 761 in lib/api-client/gmail-client.js

View workflow job for this annotation

GitHub Actions / test (16.x, ubuntu-20.04)

Unexpected require()

Check failure on line 761 in lib/api-client/gmail-client.js

View workflow job for this annotation

GitHub Actions / test (18.x, ubuntu-20.04)

Unexpected require()

Check failure on line 761 in lib/api-client/gmail-client.js

View workflow job for this annotation

GitHub Actions / test (20.x, ubuntu-20.04)

Unexpected require()

let mailboxes = await gmailClient.listMailboxes();
console.log(mailboxes);
let gmailClient = new GmailClient('andris', { redis });

let messages = await gmailClient.listMessages({ path: 'INBOX' });
console.log(JSON.stringify(messages, false, 2));
let mailboxes = await gmailClient.listMailboxes();
console.log(mailboxes);

let deleted = false;
let messages = await gmailClient.listMessages({ path: 'INBOX' });
console.log(JSON.stringify(messages, false, 2));

for (let msg of messages) {
if (/testkiri/i.test(msg.subject) && !deleted) {
deleted = true;
let deleted = false;

console.log('DELETING', msg.id);
let y = await gmailClient.deleteMessage(msg.id, true);
console.log('DELETE RESULT', y);
}
for (let msg of messages) {
if (/testkiri/i.test(msg.subject) && !deleted) {
deleted = true;

console.log('DELETING', msg.id);
let y = await gmailClient.deleteMessage(msg.id, true);
console.log('DELETE RESULT', y);
}

if (msg.attachments && msg.attachments.length) {
await gmailClient.getMessage(msg.id, { textType: '*' });
if (msg.attachments && msg.attachments.length) {
await gmailClient.getMessage(msg.id, { textType: '*' });

const textContent = await gmailClient.getText(msg.text.id, { textType: '*' });
console.log('TEXT CONTENT', textContent);
const textContent = await gmailClient.getText(msg.text.id, { textType: '*' });
console.log('TEXT CONTENT', textContent);

console.log('MOVE MESSAGE');
let moveRes = await gmailClient.moveMessage(msg.id, { path: 'Inbox' });
console.log('MOVE RES', moveRes);
console.log('MOVE MESSAGE');
let moveRes = await gmailClient.moveMessage(msg.id, { path: 'Inbox' });
console.log('MOVE RES', moveRes);

let raw = await gmailClient.getRawMessage(msg.id);
await fs.promises.writeFile(`/Users/andris/Desktop/${msg.id}.eml`, raw);
for (let a of msg.attachments) {
let attachment = await gmailClient.getAttachment(a.id);
console.log(attachment);
let s = fs.createWriteStream(`/Users/andris/Desktop/${a.filename}`);
let raw = await gmailClient.getRawMessage(msg.id);
await fs.promises.writeFile(`/Users/andris/Desktop/${msg.id}.eml`, raw);
for (let a of msg.attachments) {
let attachment = await gmailClient.getAttachment(a.id);
console.log(attachment);
let s = fs.createWriteStream(`/Users/andris/Desktop/${a.filename}`);

console.log('PIPING TO STREAM');
await new Promise((r, e) => {
s.once('finish', r);
s.once('error', e);
console.log('PIPING TO STREAM');
await new Promise((r, e) => {
s.once('finish', r);
s.once('error', e);

s.write(attachment.data);
s.end();
});
console.log('DONE');
s.write(attachment.data);
s.end();
});
console.log('DONE');

//await fs.promises.writeFile(`/Users/andris/Desktop/${a.filename}`, attachment);
process.exit();
//await fs.promises.writeFile(`/Users/andris/Desktop/${a.filename}`, attachment);
process.exit();
}
}
}
}
};
};

main()
.catch(err => console.error(util.inspect(err, false, 22)))
.finally(() => process.exit());
main()
.catch(err => console.error(require('util').inspect(err, false, 22)))

Check failure on line 817 in lib/api-client/gmail-client.js

View workflow job for this annotation

GitHub Actions / test (16.x, ubuntu-20.04)

Unexpected require()

Check failure on line 817 in lib/api-client/gmail-client.js

View workflow job for this annotation

GitHub Actions / test (18.x, ubuntu-20.04)

Unexpected require()

Check failure on line 817 in lib/api-client/gmail-client.js

View workflow job for this annotation

GitHub Actions / test (20.x, ubuntu-20.04)

Unexpected require()
.finally(() => process.exit());
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"scripts": {
"start": "node server.js",
"dev": "EE_OPENAPI_VERBOSE=true EENGINE_LOG_RAW=true node --tls-keylog=keylog.txt server --dbs.redis='redis://127.0.0.1:6379/9' --api.port=7003 --api.host=0.0.0.0 | tee $HOME/ee.log.dev.txt | pino-pretty",
"single": "EE_OPENAPI_VERBOSE=true EENGINE_LOG_RAW=true EENGINE_WORKERS=1 node --inspect server --dbs.redis='redis://127.0.0.1:6379/10' --api.port=7009 --api.host=0.0.0.0 | tee $HOME/ee.log.single.txt | pino-pretty",
"single": "EE_OPENAPI_VERBOSE=true EENGINE_LOG_RAW=true EENGINE_WORKERS=1 node --inspect server --dbs.redis='redis://127.0.0.1:6379/10' --api.port=7003 --api.host=0.0.0.0 | tee $HOME/ee.log.single.txt | pino-pretty",
"gmail": "EE_OPENAPI_VERBOSE=true EENGINE_LOG_RAW=true EENGINE_FEATURE_GMAIL_API=true EENGINE_WORKERS=1 node --inspect server --dbs.redis='redis://127.0.0.1:6379/11' --api.port=7003 --api.host=0.0.0.0 | tee $HOME/ee.log.gmail.txt | pino-pretty",
"test": "grunt && node --test test/",
"swagger": "./getswagger.sh",
Expand Down
47 changes: 32 additions & 15 deletions workers/imap.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ if (readEnvValue('BUGSNAG_API_KEY')) {
}

const { Connection } = require('../lib/connection');
const { GmailClient } = require('../lib/api-client/gmail-client');
const { Account } = require('../lib/account');
const { oauth2Apps } = require('../lib/oauth2-apps');
const { redis, notifyQueue, submitQueue, documentsQueue, getFlowProducer } = require('../lib/db');
const { MessagePortWritable } = require('../lib/message-port-stream');
const { getESClient } = require('../lib/document-store');
Expand Down Expand Up @@ -138,22 +140,37 @@ class ConnectionHandler {
});

this.accounts.set(account, accountObject);
accountObject.connection = new Connection({
account,
accountObject,
redis,
secret,
notifyQueue,
submitQueue,
documentsQueue,
flowProducer,
accountLogger,
call: msg => this.call(msg),
logRaw: EENGINE_LOG_RAW
});
accountObject.logger = accountObject.connection.logger;

let accountData = await accountObject.loadAccountData();
const accountData = await accountObject.loadAccountData();

if (accountData.oauth2 && accountData.oauth2.auth) {
const oauth2App = await oauth2Apps.get(accountData.oauth2.provider);
if (oauth2App.baseScopes === 'api') {
// Use API instead of IMAP
accountObject.connection = new GmailClient(account, {
redis,
accountLogger
});
accountData.state = 'connected';
}
}

if (!accountObject.connection) {
accountObject.connection = new Connection({
account,
accountObject,
redis,
secret,
notifyQueue,
submitQueue,
documentsQueue,
flowProducer,
accountLogger,
call: msg => this.call(msg),
logRaw: EENGINE_LOG_RAW
});
accountObject.logger = accountObject.connection.logger;
}

if (accountData.state) {
await redis.hSetExists(accountObject.connection.getAccountKey(), 'state', accountData.state);
Expand Down

0 comments on commit abd91e8

Please sign in to comment.