Skip to content

Commit

Permalink
issue #4096 refactored passphraseGet() as well as additional tests (#…
Browse files Browse the repository at this point in the history
…4109)

* test cancelling passphrase dialog in compose

* Change the 'keyup' events handlers to 'input' because text content can be changed with the mouse (#4100)

* Change the 'keyup' handlers to 'input' because text content can be changed with the mouse

* trigger 'input' events

* Upgrade to Puppeteer 11 (#4093)

* Upgrade to Puppeteer 11

* unsuccessful attempt to refactor createSecureDraft, add todo for the future

* Refactor pageHasSecureDraft() to use getFrame() instead of opening new tab

* use getFrame() in Thunderbird tests

* workaround sending in the 'secure reply btn, reply draft' test

* Simplify openGmailPage()

* fail faster - add timeout param to waitAndClick()

* wip

* handle 'Node is either not clickable' error

* wait longer for @action-step0 and @action-step1

* wait longer for @input-compatibility-fix-expire-years

* wip

* always delete local draft after sending

* cleanup

* rename mock live test

* log

* fix 'secure reply btn, reply draft' test

* cleanup

* do not rely on sleep timeouts

* typo

* timeout in seconds

* let composeBox: Controllable | undefined

* #4052 passphrase dialog for non-primary S/MIME signing

* Do not show post-it reminder for EKM (#4103)

* Do not show post-it reminder for EKM

* upd tests

* enterPp.expectPostitNoteReminder

* Remove expectPostitNoteReminder and related check

* Fix the 'Reply' button behavior, align it with Gmail (#4107)

* Fix the 'Reply' button behavior, align it with Gmail

* copy before iterating

* Add tests

* add clearRecipientsForReply() to initComposeBox()

* Revert

* fix the reply button behavior

* await promise

* Add live gmail test for the reply icon button

* simpler namings (#4111)

* align 'reply all' behavior with Gmail

Co-authored-by: Roman <rrrooommmaaa@mail.ru>

* issue #3885 add checkbox per email for attester key submission (#3907)

* add checkbox per email for attester key submission

* fixes tslint error

* fixes tslint error

* add xss-escaped comment to pass pattern checks

* Added test for issue-3885 selectable email aliases to submit on attester

* Added a private key with two UIDs

* refactor data-test naming and add multi email alias account to google-endpoint.ts

* fix tslint error

* fix failing test on setup-page-recipe

* Added test for importing key with multiple email alias (incomplete)

* fixes tslint formatting error

* complete test for importing key with multiple email alias

* rename data-test and class container

* rename data-test and class container

* separate the test to CONSUMER-MOCK test variant

* move the render display function to key-import-ui.ts

* emails for checkboxes are default to 'unchecked' state

* remove accidental console.log

* Added automatic check/uncheck when an email is present.

* fixes tslint by adding interface property

* add event of keyup paste and change to manipulate checkboxes

* change button color from gray to green when valid private key

* remove checkEmailAliasIfPresent and uses fillOnly

* use fillOnly completely

* bring back checkEmailAliasIfPresent and wrap it in fillOnly

* rename css class name to avoid interfering with any className based checking.

* added key with multiple aliases

* simplified code and move data-test to label input

* corrected any type to string

* remove spagetti code and better checking for submitkeyforaddrs

* exclude email (uncheck checkbox) before submitting

* attester pubkey for multi alias user (failing)

* added test "setup - imported key from a file with multiple alias"

* added test if excluded email was submitted from the attester

* collect submitted keys from attester

* patch data-test (selector) trasnformer to match/replace any provided selector

* move data-test directly to the input

* fix inconsistency in checking detected email alias

* manipulate test key and added 1 UID

* complete neccessary tests by checking default detected key states

* refactor email alias process [floating-promise-error] in constructor setup.ts (key-import-ui initPrvImportSrcForm)

* remove comment

* parse aliases via already rendered input checkbox

* patched 'saveKeysAndPassPhrase' on setup.ts

* fix conflict

* fix conflict

* fixes eslint

* added callback when performing tests

* remove unnecessary undefined initialization

* fix pubkey definition

* uncheck the checkbox when submit pubkey was set to false

* better flow for pubkey submission

* check for checkbox state in the first run

* fix test title typo

* reverted back changes [proposed solution]

* clean up setup procedure

Co-authored-by: Roman Shevchenko <rrrooommmaaa@mail.ru>
Co-authored-by: Mart Gil Robles <mart@Marts-MacBook-Air.local>
Co-authored-by: Tom J <6306961+tomholub@users.noreply.github.com>
Co-authored-by: Tom <tom@flowcrypt.com>
Co-authored-by: Tom J <tom@holub.me>

* issue #4097 add warning when manually importing public keys (#4110)

* issue #4097 add warning when manually importing public keys

* Improve wording, make import buttons orange

* more flexible google mock

* allow opening a draft compose box based from inbox.ts in debug mode

* fix

* added filePath option for addKeyTest

* test loading draft with a forgotten non-primary passphrase

* merge fix

* merge fix

* test fix

* remark

Co-authored-by: Limon Monte <limon.monte@gmail.com>
Co-authored-by: martgil <46025304+martgil@users.noreply.github.com>
Co-authored-by: Mart Gil Robles <mart@Marts-MacBook-Air.local>
Co-authored-by: Tom J <6306961+tomholub@users.noreply.github.com>
Co-authored-by: Tom <tom@flowcrypt.com>
Co-authored-by: Tom J <tom@holub.me>
  • Loading branch information
7 people committed Nov 16, 2021
1 parent f84a6d4 commit b9bf72c
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 89 deletions.
51 changes: 25 additions & 26 deletions extension/chrome/elements/compose-modules/compose-draft-module.ts
Expand Up @@ -13,7 +13,7 @@ import { Env } from '../../../js/common/browser/env.js';
import { GlobalStore } from '../../../js/common/platform/store/global-store.js';
import { GmailRes } from '../../../js/common/api/email-provider/gmail/gmail-parser.js';
import { MsgBlockParser } from '../../../js/common/core/msg-block-parser.js';
import { MsgUtil } from '../../../js/common/core/crypto/pgp/msg-util.js';
import { DecryptErrTypes, MsgUtil } from '../../../js/common/core/crypto/pgp/msg-util.js';
import { Ui } from '../../../js/common/browser/ui.js';
import { Str, Url } from '../../../js/common/core/common.js';
import { Xss } from '../../../js/common/platform/xss.js';
Expand All @@ -22,6 +22,7 @@ import { ComposeView } from '../compose.js';
import { KeyStore } from '../../../js/common/platform/store/key-store.js';
import { KeyUtil } from '../../../js/common/core/crypto/key.js';
import { SendableMsg, InvalidRecipientError } from '../../../js/common/api/email-provider/sendable-msg.js';
import { PassphraseStore } from '../../../js/common/platform/store/passphrase-store.js';

export class ComposeDraftModule extends ViewModule<ComposeView> {

Expand Down Expand Up @@ -288,28 +289,27 @@ export class ComposeDraftModule extends ViewModule<ComposeView> {
return await this.abortAndRenderReplyMsgComposeTableIfIsReplyBox('!rawBlock');
}
const encryptedData = rawBlock.content instanceof Buf ? rawBlock.content : Buf.fromUtfStr(rawBlock.content);
const passphrase = await this.view.storageModule.passphraseGet();
if (typeof passphrase !== 'undefined') {
const decrypted = await MsgUtil.decryptMessage({ kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.view.acctEmail), encryptedData });
if (!decrypted.success) {
return await this.abortAndRenderReplyMsgComposeTableIfIsReplyBox('!decrypted.success');
const decrypted = await MsgUtil.decryptMessage({ kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.view.acctEmail), encryptedData });
if (!decrypted.success) {
if (decrypted.error.type === DecryptErrTypes.needPassphrase) {
// "close" button will wipe this frame out, so no need to exit the recursion
await this.renderPPDialogAndWaitWhenPPEntered(decrypted.longids.needPassphrase);
await this.decryptAndRenderDraft(encrypted);
}
this.wasMsgLoadedFromDraft = true;
this.view.S.cached('prompt').css({ display: 'none' });
const { blocks, isRichText } = await MsgBlockParser.fmtDecryptedAsSanitizedHtmlBlocks(decrypted.content, 'IMG-KEEP');
const sanitizedContent = blocks.find(b => b.type === 'decryptedHtml')?.content;
if (!sanitizedContent) {
return await this.abortAndRenderReplyMsgComposeTableIfIsReplyBox('!sanitizedContent');
}
if (isRichText) {
this.view.sendBtnModule.popover.toggleItemTick($('.action-toggle-richtext-sending-option'), 'richtext', true);
}
this.view.inputModule.inputTextHtmlSetSafely(sanitizedContent.toString());
this.view.inputModule.squire.focus();
} else {
await this.renderPPDialogAndWaitWhenPPEntered();
await this.decryptAndRenderDraft(encrypted);
return await this.abortAndRenderReplyMsgComposeTableIfIsReplyBox('!decrypted.success');
}
this.wasMsgLoadedFromDraft = true;
this.view.S.cached('prompt').css({ display: 'none' });
const { blocks, isRichText } = await MsgBlockParser.fmtDecryptedAsSanitizedHtmlBlocks(decrypted.content, 'IMG-KEEP');
const sanitizedContent = blocks.find(b => b.type === 'decryptedHtml')?.content;
if (!sanitizedContent) {
return await this.abortAndRenderReplyMsgComposeTableIfIsReplyBox('!sanitizedContent');
}
if (isRichText) {
this.view.sendBtnModule.popover.toggleItemTick($('.action-toggle-richtext-sending-option'), 'richtext', true);
}
this.view.inputModule.inputTextHtmlSetSafely(sanitizedContent.toString());
this.view.inputModule.squire.focus();
};

private hasBodyChanged = (msgBody: string) => {
Expand All @@ -335,8 +335,8 @@ export class ComposeDraftModule extends ViewModule<ComposeView> {
return false;
};

private renderPPDialogAndWaitWhenPPEntered = async () => {
const promptText = `<div>Waiting for <a href="#" class="action_open_passphrase_dialog">pass phrase</a> to open draft..</div>`;
private renderPPDialogAndWaitWhenPPEntered = async (longids: string[]) => {
const promptText = `<div>Waiting for <a href="#" data-test="action-open-passphrase-dialog" class="action_open_passphrase_dialog">pass phrase</a> to open draft..</div>`;
if (this.view.isReplyBox) {
Xss.sanitizeRender(this.view.S.cached('prompt'), promptText).css({ display: 'block' });
this.view.sizeModule.resizeComposeBox();
Expand All @@ -345,11 +345,10 @@ export class ComposeDraftModule extends ViewModule<ComposeView> {
BrowserMsg.send.setActiveWindow(this.view.parentTabId, { frameId: this.view.frameId });
}
this.view.S.cached('prompt').find('a.action_open_passphrase_dialog').click(this.view.setHandler(async () => {
const primaryKi = await KeyStore.getFirstRequired(this.view.acctEmail);
BrowserMsg.send.passphraseDialog(this.view.parentTabId, { type: 'draft', longids: [primaryKi.longid] });
BrowserMsg.send.passphraseDialog(this.view.parentTabId, { type: 'draft', longids });
}));
this.view.S.cached('prompt').find('a.action_close').click(this.view.setHandler(() => this.view.renderModule.closeMsg()));
await this.view.storageModule.whenMasterPassphraseEntered();
await PassphraseStore.waitUntilPassphraseChanged(this.view.acctEmail, longids, 1000, this.view.ppChangedPromiseCancellation);
};

private abortAndRenderReplyMsgComposeTableIfIsReplyBox = async (reason: string) => {
Expand Down
Expand Up @@ -2,7 +2,7 @@

'use strict';

import { Bm, BrowserMsg } from '../../../js/common/browser/browser-msg.js';
import { BrowserMsg } from '../../../js/common/browser/browser-msg.js';
import { KeyInfo, KeyUtil, Key, PubkeyResult } from '../../../js/common/core/crypto/key.js';
import { ApiErr } from '../../../js/common/api/shared/api-error.js';
import { Assert } from '../../../js/common/assert.js';
Expand All @@ -18,18 +18,6 @@ import { PassphraseStore } from '../../../js/common/platform/store/passphrase-st
import { Settings } from '../../../js/common/settings.js';

export class ComposeStorageModule extends ViewModule<ComposeView> {

private passphraseInterval: number | undefined;

public setHandlers = () => {
BrowserMsg.addListener('passphrase_entry', async ({ entered }: Bm.PassphraseEntry) => {
if (!entered) {
clearInterval(this.passphraseInterval);
this.view.sendBtnModule.resetSendBtn();
}
});
};

// if `type` is supplied, returns undefined if no keys of this type are found
public getKeyOptional = async (senderEmail: string | undefined, type?: 'openpgp' | 'x509' | undefined) => {
const keys = await KeyStore.getTypedKeyInfos(this.view.acctEmail);
Expand Down Expand Up @@ -97,7 +85,7 @@ export class ComposeStorageModule extends ViewModule<ComposeView> {
return Object.entries(resultsPerType).sort((a, b) => rank(a) - rank(b))[0][1];
};

public passphraseGet = async (senderKi?: { longid: string }) => {
public passphraseGet = async (senderKi: { longid: string }) => {
if (!senderKi) {
senderKi = await KeyStore.getFirstRequired(this.view.acctEmail);
}
Expand All @@ -108,10 +96,11 @@ export class ComposeStorageModule extends ViewModule<ComposeView> {
const prv = await KeyUtil.parse(senderKi.private);
const passphrase = await this.passphraseGet(senderKi);
if (typeof passphrase === 'undefined' && !prv.fullyDecrypted) {
BrowserMsg.send.passphraseDialog(this.view.parentTabId, { type: 'sign', longids: [senderKi.longid] });
if ((typeof await this.whenMasterPassphraseEntered(60)) !== 'undefined') { // pass phrase entered
const longids = [senderKi.longid];
BrowserMsg.send.passphraseDialog(this.view.parentTabId, { type: 'sign', longids });
if (await PassphraseStore.waitUntilPassphraseChanged(this.view.acctEmail, longids, 1000, this.view.ppChangedPromiseCancellation)) {
return await this.decryptSenderKey(senderKi);
} else { // timeout - reset - no passphrase entered
} else { // reset - no passphrase entered
this.view.sendBtnModule.resetSendBtn();
return undefined;
}
Expand Down Expand Up @@ -206,23 +195,6 @@ export class ComposeStorageModule extends ViewModule<ComposeView> {
}
};

public whenMasterPassphraseEntered = async (secondsTimeout?: number): Promise<string | undefined> => {
clearInterval(this.passphraseInterval);
const timeoutAt = secondsTimeout ? Date.now() + secondsTimeout * 1000 : undefined;
return await new Promise(resolve => {
this.passphraseInterval = Catch.setHandledInterval(async () => {
const passphrase = await this.passphraseGet();
if (typeof passphrase !== 'undefined') {
clearInterval(this.passphraseInterval);
resolve(passphrase);
} else if (timeoutAt && Date.now() > timeoutAt) {
clearInterval(this.passphraseInterval);
resolve(undefined);
}
}, 1000);
});
};

public refreshAccountAndSubscriptionIfLoggedIn = async () => {
const auth = await AcctStore.authInfo(this.view.acctEmail);
if (auth.uuid) {
Expand Down
10 changes: 8 additions & 2 deletions extension/chrome/elements/compose.ts
Expand Up @@ -8,7 +8,7 @@ import { Assert } from '../../js/common/assert.js';
import { Bm, BrowserMsg } from '../../js/common/browser/browser-msg.js';
import { Gmail } from '../../js/common/api/email-provider/gmail/gmail.js';
import { Ui } from '../../js/common/browser/ui.js';
import { Url } from '../../js/common/core/common.js';
import { PromiseCancellation, Url } from '../../js/common/core/common.js';
import { View } from '../../js/common/view.js';
import { XssSafeFactory } from '../../js/common/xss-safe-factory.js';
import { opgp } from '../../js/common/core/crypto/pgp/openpgpjs-custom.js';
Expand Down Expand Up @@ -47,6 +47,7 @@ export class ComposeView extends View {
public skipClickPrompt: boolean;
public draftId: string;
public threadId: string = '';
public ppChangedPromiseCancellation: PromiseCancellation = { cancel: false };

public scopes!: Scopes;
public tabId!: string;
Expand Down Expand Up @@ -187,6 +188,12 @@ export class ComposeView extends View {
this.S.cached('input_to').focus();
}
});
BrowserMsg.addListener('passphrase_entry', async ({ entered }: Bm.PassphraseEntry) => {
if (!entered) {
this.ppChangedPromiseCancellation.cancel = true; // update original object which is monitored by a promise
this.ppChangedPromiseCancellation = { cancel: false }; // set to a new, not yet used object
}
});
BrowserMsg.listen(this.parentTabId);
const setActiveWindow = this.setHandler(async () => { BrowserMsg.send.setActiveWindow(this.parentTabId, { frameId: this.frameId }); });
this.S.cached('body').on('focusin', setActiveWindow);
Expand All @@ -197,7 +204,6 @@ export class ComposeView extends View {
this.myPubkeyModule.setHandlers();
this.pwdOrPubkeyContainerModule.setHandlers();
this.sizeModule.setHandlers();
this.storageModule.setHandlers();
this.recipientsModule.setHandlers();
this.sendBtnModule.setHandlers();
this.draftModule.setHandlers(); // must be the last one so that 'onRecipientAdded/draftSave' to works properly
Expand Down
10 changes: 9 additions & 1 deletion extension/chrome/settings/inbox/inbox.ts
Expand Up @@ -34,6 +34,7 @@ export class InboxView extends View {
public readonly labelId: string;
public readonly threadId: string | undefined;
public readonly showOriginal: boolean;
public readonly debug: boolean;
public readonly S: SelCache;
public readonly gmail: Gmail;

Expand All @@ -45,11 +46,12 @@ export class InboxView extends View {

constructor() {
super();
const uncheckedUrlParams = Url.parse(['acctEmail', 'labelId', 'threadId', 'showOriginal']);
const uncheckedUrlParams = Url.parse(['acctEmail', 'labelId', 'threadId', 'showOriginal', 'debug']);
this.acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail');
this.labelId = uncheckedUrlParams.labelId ? String(uncheckedUrlParams.labelId) : 'INBOX';
this.threadId = Assert.urlParamRequire.optionalString(uncheckedUrlParams, 'threadId');
this.showOriginal = uncheckedUrlParams.showOriginal === true;
this.debug = uncheckedUrlParams.debug === true;
this.S = Ui.buildJquerySels({ threads: '.threads', thread: '.thread', body: 'body' });
this.gmail = new Gmail(this.acctEmail);
this.inboxMenuModule = new InboxMenuModule(this);
Expand Down Expand Up @@ -154,6 +156,12 @@ export class InboxView extends View {
BrowserMsg.addListener('show_attachment_preview', async ({ iframeUrl }: Bm.ShowAttachmentPreview) => {
await Ui.modal.attachmentPreview(iframeUrl);
});
if (this.debug) {
BrowserMsg.addListener('open_compose_window', async ({ draftId }: Bm.ComposeWindowOpenDraft) => {
console.log('received open_compose_window');
this.injector.openComposeWin(draftId);
});
}
};

}
Expand Down
6 changes: 3 additions & 3 deletions extension/chrome/settings/modules/add_key.htm
Expand Up @@ -26,9 +26,9 @@ <h1>Add Private Key <span id="spinner_container"></span></h1>
<ul style="list-style: none; margin-left: 0; padding-left: 0;" class="source_selector display_none">
<li><input type="radio" name="source" id="source_backup" value="backup"> <label for="source_backup">Load from
backup</label></li>
<li><input type="radio" name="source" id="source_file" value="file"> <label for="source_file">Load from a
<li><input type="radio" name="source" id="source_file" data-test="source-file" value="file"> <label for="source_file">Load from a
file</label></li>
<li><input type="radio" name="source" id="source_paste" value="paste"> <label for="source_paste">Paste
<li><input type="radio" name="source" id="source_paste" data-test="source-paste" value="paste"> <label for="source_paste">Paste
armored key directly</label></li>
</ul>
</div>
Expand All @@ -38,7 +38,7 @@ <h1>Add Private Key <span id="spinner_container"></span></h1>
</div>
<div class="source_paste_container display_none">
<div class="line">
<textarea class="input_private_key" placeholder="ASCII Armored Private Key"></textarea>
<textarea class="input_private_key" data-test="input-armored-key" placeholder="ASCII Armored Private Key"></textarea>
</div>
<div class="line display_none unprotected_key_create_pass_phrase" style="text-align: left; padding-left: 40px;">
This key is unprotected. Create a pass phrase or <a href="#" class="action_use_random_pass_phrase"
Expand Down
3 changes: 2 additions & 1 deletion extension/js/common/browser/browser-msg.ts
Expand Up @@ -40,6 +40,7 @@ export namespace Bm {
export type RenderPublicKeys = { afterFrameId: string, publicKeys: string[], traverseUp?: number };
export type SubscribeDialog = { isAuthErr?: boolean };
export type ComposeWindow = { frameId: string };
export type ComposeWindowOpenDraft = { draftId: string };
export type ReinsertReplyBox = { replyMsgId: string };
export type AddPubkeyDialog = { emails: string[] };
export type Reload = { advanced?: boolean };
Expand Down Expand Up @@ -97,7 +98,7 @@ export namespace Bm {

export type AnyRequest = PassphraseEntry | StripeResult | OpenPage | OpenGoogleAuthDialog | Redirect | Reload |
AddPubkeyDialog | ReinsertReplyBox | ComposeWindow | ScrollToReplyBox | ScrollToCursorInReplyBox | SubscribeDialog |
RenderPublicKeys | NotificationShowAuthPopupNeeded |
RenderPublicKeys | NotificationShowAuthPopupNeeded | ComposeWindowOpenDraft |
NotificationShow | PassphraseDialog | PassphraseDialog | Settings | SetCss | AddOrRemoveClass | ReconnectAcctAuthPopup |
Db | InMemoryStoreSet | InMemoryStoreGet | StoreGlobalGet | StoreGlobalSet | StoreAcctGet | StoreAcctSet | KeyParse |
PgpMsgDecrypt | PgpMsgDiagnoseMsgPubkeys | PgpMsgVerifyDetached | PgpHashChallengeAnswer | PgpMsgType | Ajax |
Expand Down
25 changes: 21 additions & 4 deletions test/source/mock/google/google-data.ts
Expand Up @@ -11,7 +11,7 @@ type GmailMsg$payload$part = { partId?: string, body?: GmailMsg$payload$body, fi
type GmailMsg$payload = { partId?: string, filename?: string, parts?: GmailMsg$payload$part[], headers?: GmailMsg$header[], mimeType?: string, body?: GmailMsg$payload$body };
type GmailMsg$labelId = 'INBOX' | 'UNREAD' | 'CATEGORY_PERSONAL' | 'IMPORTANT' | 'SENT' | 'CATEGORY_UPDATES' | 'DRAFT';
type GmailThread = { historyId: string; id: string; snippet: string; };
type Label = { id: string, name: "CATEGORY_SOCIAL", messageListVisibility: "hide", labelListVisibility: "labelHide", type: 'system' };
type Label = { id: string, name: string, messageListVisibility: 'show' | 'hide', labelListVisibility: 'labelShow' | 'labelHide', type: 'system' };
type AcctDataFile = { messages: GmailMsg[]; drafts: GmailMsg[], attachments: { [id: string]: { data: string, size: number, filename?: string } }, labels: Label[] };
type ExportedMsg = { acctEmail: string, full: GmailMsg, raw: GmailMsg, attachments: { [id: string]: { data: string, size: number } } };

Expand Down Expand Up @@ -111,7 +111,13 @@ export class GoogleData {

public static withInitializedData = async (acct: string): Promise<GoogleData> => {
if (typeof DATA[acct] === 'undefined') {
const acctData: AcctDataFile = { drafts: [], messages: [], attachments: {}, labels: [] };
const acctData: AcctDataFile = {
drafts: [], messages: [], attachments: {}, labels:
[
{ id: 'INBOX', name: 'Inbox', messageListVisibility: 'show', labelListVisibility: 'labelShow', type: 'system' },
{ id: 'DRAFT', name: 'Drafts', messageListVisibility: 'show', labelListVisibility: 'labelShow', type: 'system' }
]
};
const dir = GoogleData.exportedMsgsPath;
const filenames: string[] = await new Promise((res, rej) => readdir(dir, (e, f) => e ? rej(e) : res(f)));
const filePromises = filenames.map(f => new Promise((res, rej) => readFile(dir + f, (e, d) => e ? rej(e) : res(d))));
Expand Down Expand Up @@ -219,6 +225,10 @@ export class GoogleData {
});
};

public getMessagesAndDraftsByThread = (threadId: string) => {
return this.getMessagesAndDrafts().filter(m => m.threadId === threadId);
};

public getMessagesByThread = (threadId: string) => {
return DATA[this.acct].messages.filter(m => m.threadId === threadId);
};
Expand Down Expand Up @@ -266,16 +276,23 @@ export class GoogleData {
return DATA[this.acct].labels;
};

public getThreads = () => {
public getThreads = (labelIds: string[] = []) => {
const threads: GmailThread[] = [];
for (const thread of DATA[this.acct].messages.map(m => ({ historyId: m.historyId, id: m.threadId!, snippet: `MOCK SNIPPET: ${GoogleData.msgSubject(m)}` }))) {
for (const thread of this.getMessagesAndDrafts().
filter(m => labelIds.length ? (m.labelIds || []).some(l => labelIds.includes(l)) : true).
map(m => ({ historyId: m.historyId, id: m.threadId!, snippet: `MOCK SNIPPET: ${GoogleData.msgSubject(m)}` }))) {
if (thread.id && !threads.map(t => t.id).includes(thread.id)) {
threads.push(thread);
}
}
return threads;
};

// returns ordinary messages and drafts
private getMessagesAndDrafts = () => {
return DATA[this.acct].messages.concat(DATA[this.acct].drafts);
};

private searchMessagesBySubject = (subject: string) => {
subject = subject.trim().toLowerCase();
const messages = DATA[this.acct].messages.filter(m => GoogleData.msgSubject(m).toLowerCase().includes(subject));
Expand Down

0 comments on commit b9bf72c

Please sign in to comment.