chadxz / imap-simple

Wrapper over node-imap, providing a simpler api for common use cases
MIT License
243 stars 80 forks source link

Accessing uidvalidity of a mailbox #87

Closed anl176 closed 3 years ago

anl176 commented 3 years ago

I am trying to access the uidvalidity of a mailbox but do not see any documented way to do so with imap-simple. I am connecting to my mailbox with the following.

import imaps from 'imap-simple';

let connection = await imaps.connect(options);
connection.openBox('INBOX').then(() => {
    // Find a way to interact with the mailbox
});

I am using Typescript and by analyzing the type definitions of imap-simple and node-imap it seems like my ultimate goal is to get my hands on an node-imap object that implements the Box interface. node-imap would allow me to get those through callbacks with openBox.

export interface Box {
        ...
        /** A 32-bit number that can be used to determine if UIDs in this mailbox have changed since the last time this mailbox was opened. */
        uidvalidity: number;
        ...
        };
    }

declare class Connection extends EventEmitter implements Connection.MessageFunctions {
        ...
        openBox(mailboxName: string, callback: (error: Error, mailbox: Connection.Box) => void): void;
        openBox(mailboxName: string, openReadOnly: boolean, callback: (error: Error, mailbox: Connection.Box) => void): void;
        openBox(mailboxName: string, openReadOnly: boolean, modifiers: Object, callback: (error: Error, mailbox: Connection.Box) => void): void
        ...
}

imap-simple does not seem to have any way to reach that endpoint. The type definitions for openBox callbacks only returns the boxname.

export class ImapSimple extends EventEmitter {
    constructor(imap: Imap);

    /** Open a mailbox, calling the provided callback with signature (err, boxName), or resolves the returned promise with boxName. */
    openBox(boxName: string, callback: (err: Error, boxName: string) => void): void;
    openBox(boxName: string): Promise<string>;
}

Now if I were to look at connection at runtime I see that it has access to it's node-imap connection and I can do something like this if I disregard the type

import imaps from 'imap-simple';

let connection: any = await imaps.connect(options);
await connection.openBox('INBOX');

console.log('Real Imap Connection is')
console.log(connection.imap);

console.log('uidvalidity is')
console.log(connection.imap._box.name)
console.log(connection.imap._box.uidvalidity)

This is my current workaround but I lose the power of Typescript (though if I knew how to add onto the @types/imap-simple definitions and ship those with my site I wouldn't be having this problem). I can probably create some Typescript boilerplate to create my own interface to extend ImapSimple and include the { imap: Connection } member field but I'm not sure how reliable imap._box is to get the uidvalidity. Is there a recommended way through imap-simple to get it?

I could just use node-imap to open up a connection and rely on their callbacks but I am trying to avoid opening up unnecessary connections as I know O365 can punish you hard for this.

Patrick-Remy commented 3 years ago

Not ideal, but for a little more typescript support, you could use:

import type { Box } from 'imap'
import imaps from 'imap-simple'

const connection = await imaps.connect(options);
await connection.openBox('INBOX')

const mailbox: Box = (connection as any).imap._box
console.log(mailbox.uidvalidity)
soanvig commented 3 years ago

This seems like a real issue, since uidvalidity is second to uid most important information (for practical use over time).

I wonder why it is not public property. node-imap has it public. Relying on .imap._box seems like a bad idea, since the API can change unexpectedly.

I can make PR, but first it would be nice to know, why this isn't public.

Patrick-Remy commented 3 years ago

I think because it is included in the return value of openBox (for node-imap), but imap-simple instead returns the name of the Box that was openened (most time same as provided as arg).

andris9 commented 3 years ago

Not really related to imap-simple but when using imapflow you can get the uidvalidity value like this (uses a real test account so you can just copy and paste the code and run without modifications):

const { ImapFlow } = require('imapflow');
const client = new ImapFlow({
    host: 'ethereal.email',
    port: 993,
    secure: true,
    auth: {
        user: 'garland.mcclure71@ethereal.email',
        pass: 'mW6e4wWWnEd3H4hT5B'
    },
    logger: false
});

const main = async () => {
    await client.connect();

    let mailbox = await client.mailboxOpen('INBOX');
    console.log(mailbox.uidValidity.toString());
    // prints "1578995505"

    await client.logout();
};

main().catch(err => {
    console.error(err);
    process.exit(1);
});