New Imap Handling
parent
01760496ed
commit
10522dc389
|
@ -0,0 +1,49 @@
|
|||
IMAP
|
||||
> Boxes
|
||||
> Messages (ordered by uid)
|
||||
|
||||
AETHER
|
||||
> Conversations
|
||||
> (Parsed) Messages (indexed by MessageID)
|
||||
|
||||
MONGO
|
||||
> Box
|
||||
> BoxID
|
||||
> UIDValidity
|
||||
> UIDNext
|
||||
> SeqNext
|
||||
> ...
|
||||
|
||||
> MsgUUID
|
||||
> MessageID
|
||||
> Box
|
||||
> UID
|
||||
|
||||
> Participant
|
||||
Name
|
||||
Image
|
||||
Addresses
|
||||
...
|
||||
|
||||
> Messages
|
||||
> MsgUUID
|
||||
> Subject
|
||||
> Participant[]
|
||||
> Time
|
||||
> ParsedContent
|
||||
|
||||
> Conversations
|
||||
> Title
|
||||
> Archived
|
||||
> LatestMessageTime
|
||||
> Participant[]
|
||||
> Messages[]
|
||||
|
||||
Box
|
||||
SEQNO / UID / SUBJECT
|
||||
|
||||
1 / 1: aaa
|
||||
2 / 3: bbb
|
||||
--- 3 / 7: ccc
|
||||
---
|
||||
--- 3 / 8: ddd
|
|
@ -0,0 +1,188 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="300mm"
|
||||
height="300mm"
|
||||
viewBox="0 0 1062.9922 1062.9926"
|
||||
id="svg4136"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="aether-3.svg">
|
||||
<defs
|
||||
id="defs4138" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#353b42"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.76576296"
|
||||
inkscape:cx="531.49606"
|
||||
inkscape:cy="531.49606"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:snap-center="true"
|
||||
inkscape:snap-text-baseline="true"
|
||||
inkscape:snap-page="false"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1004"
|
||||
inkscape:window-x="3200"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata4141">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,10.629822)">
|
||||
<circle
|
||||
style="opacity:1;fill:#ffd2e5;fill-opacity:1;stroke:none;stroke-width:4.69999981;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5295"
|
||||
cx="233.86275"
|
||||
cy="567.77429"
|
||||
r="126.25221" />
|
||||
<path
|
||||
style="fill:#ffb8d8;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 860.32246,878.93418 -687.66986,0 0,-233.22777 700.42601,0 z"
|
||||
id="path5293"
|
||||
inkscape:connector-curvature="0" />
|
||||
<circle
|
||||
style="opacity:1;fill:#ffb8d8;fill-opacity:1;stroke:none;stroke-width:4.69999981;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5287"
|
||||
cx="163.87477"
|
||||
cy="722.06995"
|
||||
r="156.9731" />
|
||||
<circle
|
||||
style="opacity:1;fill:#ffd2e5;fill-opacity:1;stroke:none;stroke-width:4.69999981;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5289"
|
||||
cx="837.90277"
|
||||
cy="494.0274"
|
||||
r="119.03172" />
|
||||
<circle
|
||||
style="opacity:1;fill:#ffb8d8;fill-opacity:1;stroke:none;stroke-width:4.69999981;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5291"
|
||||
cx="860.67151"
|
||||
cy="682.90381"
|
||||
r="196.40234" />
|
||||
<rect
|
||||
style="fill:#add2ff;fill-opacity:1;stroke:#add2ff;stroke-width:36.45346451;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4684"
|
||||
width="623.53394"
|
||||
height="385.3652"
|
||||
x="210.59846"
|
||||
y="376.41931" />
|
||||
<path
|
||||
style="fill:#c0dcff;fill-opacity:1;fill-rule:evenodd;stroke:#c0dcff;stroke-width:36.45560837;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 523.25141,579.2656 210.45456,760.37921 210.4352,427.08178 Z"
|
||||
id="path4712-3"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:#a0cbff;fill-opacity:1;fill-rule:evenodd;stroke:#a0cbff;stroke-width:36.45346451;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 523.25141,579.2656 310.88101,182.5189 0.0195,-333.29958 z"
|
||||
id="path4712"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<circle
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:35;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5121"
|
||||
cx="475.444"
|
||||
cy="495.45108"
|
||||
r="59.515858" />
|
||||
<path
|
||||
style="fill:#7ab6ff;fill-opacity:1;fill-rule:evenodd;stroke:#7ab6ff;stroke-width:36.45346451;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 210.57896,398.74408 312.67245,182.60457 310.90046,-181.58151 0,-23.36732 -623.57291,0 z"
|
||||
id="path4729"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:390.96878052px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#538fd7;fill-opacity:1;stroke:#64a4ef;stroke-width:45.7280218;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;"
|
||||
x="362.98376"
|
||||
y="521.09644"
|
||||
id="text5153-8"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0648707,0.93908114)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5155-2"
|
||||
x="362.98376"
|
||||
y="521.09644"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;fill:#538fd7;fill-opacity:1;stroke:#64a4ef;stroke-width:45.7280218;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;">A</tspan></text>
|
||||
<path
|
||||
style="fill:#538fd7;fill-opacity:1;fill-rule:evenodd;stroke:#538fd7;stroke-width:38.17992401;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 833.28864,370.91916 521.48187,170.05232 211.44219,369.79379 l 0,25.70429 621.84645,0 z"
|
||||
id="path4729-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:390.96878052px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#faeefa;fill-opacity:1;stroke:none;stroke-width:4.96040297;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="362.98376"
|
||||
y="521.09644"
|
||||
id="text5153"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0648707,0.93908114)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5155"
|
||||
x="362.98376"
|
||||
y="521.09644"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;fill:#faeefa;fill-opacity:1;stroke:none;stroke-width:4.96040297;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">A</tspan></text>
|
||||
<circle
|
||||
style="opacity:1;fill:#ff8fbe;fill-opacity:1;stroke:none;stroke-width:4.69999981;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5297-8"
|
||||
cx="848.87189"
|
||||
cy="698.89874"
|
||||
r="121.39309" />
|
||||
<circle
|
||||
style="opacity:1;fill:#ff8fbe;fill-opacity:1;stroke:none;stroke-width:4.69999981;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5297"
|
||||
cx="760.0448"
|
||||
cy="756.04407"
|
||||
r="64.481049" />
|
||||
<circle
|
||||
style="opacity:1;fill:#ff8fbe;fill-opacity:1;stroke:none;stroke-width:4.69999981;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5299"
|
||||
cx="295.05356"
|
||||
cy="764.54852"
|
||||
r="65.467445" />
|
||||
<circle
|
||||
style="opacity:1;fill:#ff8fbe;fill-opacity:1;stroke:none;stroke-width:4.69999981;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path5299-5"
|
||||
cx="214.9558"
|
||||
cy="752.37793"
|
||||
r="79.144768" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.6 KiB |
|
@ -68,6 +68,12 @@ export default function App() {
|
|||
contacts={contacts[account.id]} conversation={conversation}/>}
|
||||
</div>
|
||||
</Fragment>}
|
||||
{!account && <div class='grid place-items-center w-full bg-gray-50 pr-18'>
|
||||
<div class='hue-rotate-180 brightness-50 saturate-25'>
|
||||
<img src='../../client/res/logo.svg' width={256} height={256} alt='Loading'
|
||||
class='w-[256px] h-[256px] animate-pulse grayscale sepia'/>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"main": "build/Main.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon",
|
||||
"build": "tsc --project tsconfig.json",
|
||||
"build": "tsc --project tsconfig.json --incremental",
|
||||
"clean": "find build -name '*' -not -name 'package.json' -not -path 'build/node_modules*' -not -name 'build' -delete"
|
||||
},
|
||||
"repository": {
|
||||
|
|
|
@ -30,5 +30,8 @@
|
|||
"bugs": {
|
||||
"url": "https://github.com/Aurailus/Aether/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Aurailus/Aether#readme"
|
||||
"homepage": "https://github.com/Aurailus/Aether#readme",
|
||||
"dependencies": {
|
||||
"mongoose": "^5.13.8"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,6 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/member-ordering": "error",
|
||||
"@typescript-eslint/no-empty-function": "error",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-parameter-properties": "off",
|
||||
|
|
|
@ -237,6 +237,36 @@
|
|||
"integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
|
||||
"dev": true
|
||||
},
|
||||
"@typegoose/typegoose": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@typegoose/typegoose/-/typegoose-8.2.0.tgz",
|
||||
"integrity": "sha512-iibEA5V2FqtadURFFT2Anq/NnP8oDnlnuMFmccJsBLxanEoNH78hAbdJ9GFoND6BakFInF9BWuhZfpo2OXOb0Q==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.20",
|
||||
"loglevel": "^1.7.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"semver": "^7.3.2",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/bson": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz",
|
||||
"integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/imap": {
|
||||
"version": "0.8.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/imap/-/imap-0.8.35.tgz",
|
||||
|
@ -261,25 +291,20 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/mongodb": {
|
||||
"version": "3.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz",
|
||||
"integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==",
|
||||
"requires": {
|
||||
"@types/bson": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.2.tgz",
|
||||
"integrity": "sha512-TbG4TOx9hng8FKxaVrCisdaxKxqEwJ3zwHoCWXZ0Jw6mnvTInpaB99/2Cy4+XxpXtjNv9/TgfGSvZFyfV/t8Fw=="
|
||||
},
|
||||
"@types/webidl-conversions": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz",
|
||||
"integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
|
||||
},
|
||||
"@types/whatwg-url": {
|
||||
"version": "8.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
|
||||
"integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"@types/webidl-conversions": "*"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "4.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.3.tgz",
|
||||
|
@ -514,17 +539,55 @@
|
|||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true
|
||||
},
|
||||
"bl": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
|
||||
"integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==",
|
||||
"requires": {
|
||||
"readable-stream": "^2.3.5",
|
||||
"safe-buffer": "^5.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bluebird": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
|
||||
"integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA=="
|
||||
},
|
||||
"boolean": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.4.tgz",
|
||||
|
@ -576,21 +639,9 @@
|
|||
}
|
||||
},
|
||||
"bson": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-4.5.1.tgz",
|
||||
"integrity": "sha512-XqFP74pbTVLyLy5KFxVfTUyRrC1mgOlmu/iXHfXqfCKT59jyP9lwbotGfbN59cHBRbJSamZNkrSopjv+N0SqAA==",
|
||||
"requires": {
|
||||
"buffer": "^5.6.0"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
|
||||
"integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg=="
|
||||
},
|
||||
"buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
|
@ -1619,11 +1670,6 @@
|
|||
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
||||
|
@ -1880,8 +1926,7 @@
|
|||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
|
@ -1920,6 +1965,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"loglevel": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz",
|
||||
"integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw=="
|
||||
},
|
||||
"lowercase-keys": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
|
||||
|
@ -1930,7 +1980,6 @@
|
|||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
|
@ -2031,54 +2080,98 @@
|
|||
}
|
||||
},
|
||||
"mongodb": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.1.tgz",
|
||||
"integrity": "sha512-fbACrWEyvr6yl0sSiCGV0sqEiBwTtDJ8iSojmkDjAfw9JnOZSAkUyv9seFSPYhPPKwxp1PDtyjvBNfMDz0WBLQ==",
|
||||
"version": "3.6.11",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.11.tgz",
|
||||
"integrity": "sha512-4Y4lTFHDHZZdgMaHmojtNAlqkvddX2QQBEN0K//GzxhGwlI9tZ9R0vhbjr1Decw+TF7qK0ZLjQT292XgHRRQgw==",
|
||||
"requires": {
|
||||
"bson": "^4.5.1",
|
||||
"denque": "^1.5.0",
|
||||
"mongodb-connection-string-url": "^2.0.0",
|
||||
"bl": "^2.2.1",
|
||||
"bson": "^1.1.4",
|
||||
"denque": "^1.4.1",
|
||||
"optional-require": "^1.0.3",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"saslprep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"mongodb-connection-string-url": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.0.0.tgz",
|
||||
"integrity": "sha512-M0I1vyLoq5+HQTuPSJWbt+hIXsMCfE8sS1fS5mvP9R2DOMoi2ZD32yWqgBIITyu0dFu4qtS50erxKjvUeBiyog==",
|
||||
"requires": {
|
||||
"@types/whatwg-url": "^8.2.1",
|
||||
"whatwg-url": "^9.1.0"
|
||||
}
|
||||
},
|
||||
"mongoose": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.1.tgz",
|
||||
"integrity": "sha512-WESkAtJuJqXKjiQj+HiL3Ipr6eLWx9RIrjCE2HzxScUApnFLXSHdd5gGCeEE3Pl+qcill4fGYy/uysThCMQ6PQ==",
|
||||
"version": "5.13.8",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.8.tgz",
|
||||
"integrity": "sha512-z3d+qei9Dem/LxRcJi0cdGPKzQnYk71oHEsEfYm17JA/vLiAbJiGuBS2hW7vkd9afkPAqu3KsPZh2ax0c5iPQw==",
|
||||
"requires": {
|
||||
"bson": "^4.2.2",
|
||||
"@types/mongodb": "^3.5.27",
|
||||
"bson": "^1.1.4",
|
||||
"kareem": "2.3.2",
|
||||
"mongodb": "4.1.1",
|
||||
"mongodb": "3.6.11",
|
||||
"mongoose-legacy-pluralize": "1.0.2",
|
||||
"mpath": "0.8.3",
|
||||
"mquery": "4.0.0",
|
||||
"mquery": "3.2.5",
|
||||
"ms": "2.1.2",
|
||||
"optional-require": "1.0.x",
|
||||
"regexp-clone": "1.0.0",
|
||||
"safe-buffer": "5.2.1",
|
||||
"sift": "13.5.2",
|
||||
"sliced": "1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bson": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
|
||||
"integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg=="
|
||||
},
|
||||
"mongodb": {
|
||||
"version": "3.6.11",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.11.tgz",
|
||||
"integrity": "sha512-4Y4lTFHDHZZdgMaHmojtNAlqkvddX2QQBEN0K//GzxhGwlI9tZ9R0vhbjr1Decw+TF7qK0ZLjQT292XgHRRQgw==",
|
||||
"requires": {
|
||||
"bl": "^2.2.1",
|
||||
"bson": "^1.1.4",
|
||||
"denque": "^1.4.1",
|
||||
"optional-require": "^1.0.3",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"saslprep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"mongoose-legacy-pluralize": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz",
|
||||
"integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ=="
|
||||
},
|
||||
"mpath": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz",
|
||||
"integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA=="
|
||||
},
|
||||
"mquery": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.0.tgz",
|
||||
"integrity": "sha512-nGjm89lHja+T/b8cybAby6H0YgA4qYC/lx6UlwvHGqvTq8bDaNeCwl1sY8uRELrNbVWJzIihxVd+vphGGn1vBw==",
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz",
|
||||
"integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==",
|
||||
"requires": {
|
||||
"debug": "4.x",
|
||||
"bluebird": "3.5.1",
|
||||
"debug": "3.1.0",
|
||||
"regexp-clone": "^1.0.0",
|
||||
"safe-buffer": "5.1.2",
|
||||
"sliced": "1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
|
@ -2175,6 +2268,11 @@
|
|||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"optional-require": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz",
|
||||
"integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA=="
|
||||
},
|
||||
"optionator": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
|
||||
|
@ -2276,8 +2374,7 @@
|
|||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"dev": true
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"progress": {
|
||||
"version": "2.0.3",
|
||||
|
@ -2311,7 +2408,8 @@
|
|||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||
"dev": true
|
||||
},
|
||||
"pupa": {
|
||||
"version": "2.1.1",
|
||||
|
@ -2468,8 +2566,7 @@
|
|||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"saslprep": {
|
||||
"version": "1.0.3",
|
||||
|
@ -2793,14 +2890,6 @@
|
|||
"nopt": "~1.0.10"
|
||||
}
|
||||
},
|
||||
"tr46": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz",
|
||||
"integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==",
|
||||
"requires": {
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz",
|
||||
|
@ -2881,14 +2970,6 @@
|
|||
"is-typedarray": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"typegoose": {
|
||||
"version": "5.9.1",
|
||||
"resolved": "https://registry.npmjs.org/typegoose/-/typegoose-5.9.1.tgz",
|
||||
"integrity": "sha512-D+vMhNyZeKBZHrmJFZwOodl3T9W2NOXY+hbnW/f1n60oEL8+L15eryFc9C6fAKrlnkgpui+kdQnNXsLwx2MgCw==",
|
||||
"requires": {
|
||||
"reflect-metadata": "^0.1.13"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
|
||||
|
@ -2985,8 +3066,7 @@
|
|||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||
"dev": true
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"v8-compile-cache": {
|
||||
"version": "2.3.0",
|
||||
|
@ -2994,20 +3074,6 @@
|
|||
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
|
||||
"dev": true
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
|
||||
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w=="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-9.1.0.tgz",
|
||||
"integrity": "sha512-CQ0UcrPHyomtlOCot1TL77WyMIm/bCwrJ2D6AOKGwEczU9EpyoqAokfqrf/MioU9kHcMsmJZcg1egXix2KYEsA==",
|
||||
"requires": {
|
||||
"tr46": "^2.1.0",
|
||||
"webidl-conversions": "^6.1.0"
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
@ -3059,8 +3125,7 @@
|
|||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"yauzl": {
|
||||
"version": "2.10.0",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"scripts": {
|
||||
"dev": "nodemon",
|
||||
"lint": "eslint -c .eslintrc.js src/**/*.ts",
|
||||
"build": "tsc --project tsconfig.json"
|
||||
"build": "tsc --project tsconfig.json --incremental"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -25,7 +25,7 @@
|
|||
"src"
|
||||
],
|
||||
"ext": ".ts,.tsx,.html",
|
||||
"exec": "npm run lint & npm run build && electron .",
|
||||
"exec": "npm run lint & (npm run build && electron .)",
|
||||
"quiet": true
|
||||
},
|
||||
"bugs": {
|
||||
|
@ -45,14 +45,14 @@
|
|||
"eslint-plugin-jsdoc": "^36.0.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typegoose/typegoose": "^8.2.0",
|
||||
"common": "file:../common/build",
|
||||
"graphql": "^15.5.0",
|
||||
"imap": "^0.8.19",
|
||||
"log4js": "^6.3.0",
|
||||
"md5": "^2.3.0",
|
||||
"mongodb": "^4.1.1",
|
||||
"mongoose": "^6.0.1",
|
||||
"tslib": "^2.2.0",
|
||||
"typegoose": "^5.9.1"
|
||||
"mongodb": "^3.6.11",
|
||||
"mongoose": "^5.13.8",
|
||||
"tslib": "^2.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,148 +1,372 @@
|
|||
import Message from './Message';
|
||||
import Conversation from './Conversation';
|
||||
import Imap, { ConnectionProperties, Message as RawMessage, MailboxType } from './Imap';
|
||||
import Imap from 'imap';
|
||||
import { ObjectID } from 'mongodb'
|
||||
|
||||
interface Contact {
|
||||
name: string;
|
||||
addresses: Set<string>;
|
||||
}
|
||||
// import Message from './Message';
|
||||
// import Conversation from './Conversation';
|
||||
import Log from './Log';
|
||||
import * as DB from './data/Data';
|
||||
import ImapController from './imap/ImapController';
|
||||
|
||||
// interface Contact {
|
||||
// name: string;
|
||||
// addresses: Set<string>;
|
||||
// }
|
||||
|
||||
export default class Account {
|
||||
private imap: Imap;
|
||||
private contacts: Contact[] = [];
|
||||
private conversations: Conversation[] = [];
|
||||
|
||||
private name: string;
|
||||
private image: string;
|
||||
private address: string;
|
||||
private unread: boolean;
|
||||
private accountID: ObjectID;
|
||||
private conn: ImapController;
|
||||
// private contacts: Contact[] = [];
|
||||
// private conversations: Conversation[] = [];
|
||||
|
||||
constructor(name: string, image: string, connection: ConnectionProperties) {
|
||||
this.name = name;
|
||||
this.image = image;
|
||||
this.address = connection.username;
|
||||
this.unread = true;
|
||||
constructor(data: DB.Account) {
|
||||
this.accountID = data._id;
|
||||
this.address = data.address;
|
||||
Log.info('Created account %s', data.address);
|
||||
|
||||
this.imap = new Imap(connection);
|
||||
this.conn = new ImapController({
|
||||
user: data.address,
|
||||
password: data.password,
|
||||
host: data.host,
|
||||
port: data.port,
|
||||
tls: data.tls
|
||||
});
|
||||
}
|
||||
|
||||
async connect() {
|
||||
await this.imap.connect();
|
||||
async init() {
|
||||
await this.conn.connect();
|
||||
|
||||
const messages = await this.fetchAllMessages();
|
||||
this.conversations = this.createConversations(messages).filter(c => c.active);
|
||||
this.contacts = this.createContacts(messages);
|
||||
Log.perfStart('Synchronizing ' + this.address);
|
||||
const remoteBoxes = await this.getBoxes();
|
||||
await this.synchronizeBoxes(remoteBoxes);
|
||||
await this.synchronizeMessages(remoteBoxes);
|
||||
Log.perfEnd('Synchronizing ' + this.address);
|
||||
|
||||
|
||||
// Log.info('Connected to %s', this.data.address);
|
||||
// await this.synchronizeData();
|
||||
// Log.perfEnd('Synchronizing ' + this.data.address);
|
||||
|
||||
// const messages = await this.fetchAllMessages();
|
||||
// this.conversations = this.createConversations(messages).filter(c => c.active);
|
||||
// this.contacts = this.createContacts(messages);
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.name;
|
||||
async synchronizeBoxes(remoteBoxes: Map<string, Imap.Box>): Promise<void> {
|
||||
const currentBoxes = await DB.MailboxModel.find({ account: this.accountID });
|
||||
await Promise.all([ ...remoteBoxes.values() ].map(async box => {
|
||||
const existing = currentBoxes.filter(b => b.path === box.name)[0];
|
||||
if (!existing) await this.addNewBox(box);
|
||||
else await this.refreshExistingBox(box, existing);
|
||||
}));
|
||||
}
|
||||
|
||||
getAddress() {
|
||||
return this.address;
|
||||
private async addNewBox(box: Imap.Box) {
|
||||
await DB.MailboxModel.create({
|
||||
account: this.accountID,
|
||||
name: box.name, // TODO: This
|
||||
path: box.name,
|
||||
delimiter: '.', // TODO: and this
|
||||
type: DB.MailboxType.Inbox,
|
||||
treeTypes: new Set([ DB.MailboxType.Inbox ]),
|
||||
parent: undefined, // and this
|
||||
uidValidity: box.uidvalidity,
|
||||
uidNext: 1,
|
||||
} as DB.Create<DB.Mailbox>);
|
||||
}
|
||||
|
||||
getImage() {
|
||||
return this.image;
|
||||
private async refreshExistingBox(remote: Imap.Box, _existing: DB.Mailbox) {
|
||||
Log.debug('existing box ' + remote.name);
|
||||
}
|
||||
|
||||
hasUnreads() {
|
||||
return this.unread;
|
||||
}
|
||||
|
||||
getConversations() {
|
||||
return this.conversations;
|
||||
}
|
||||
|
||||
getContacts() {
|
||||
return this.contacts;
|
||||
}
|
||||
|
||||
getMessages(_messages: string[]): Message[] {
|
||||
return [{
|
||||
id: 'AOUEOAEu',
|
||||
date: new Date(),
|
||||
from: 'me@auri.xyz',
|
||||
to: [ 'nicole@aurailus.design' ],
|
||||
content: '<p>Lorem ipsum dolor sit amet.</p>'
|
||||
}];
|
||||
}
|
||||
|
||||
async fetchAllMessages(): Promise<RawMessage[]> {
|
||||
const boxes = (await this.imap.listBoxes()).filter(box =>
|
||||
!box.treeTypes.has(MailboxType.Spam) && !box.treeTypes.has(MailboxType.Trash));
|
||||
|
||||
let allMeta: RawMessage[] = [];
|
||||
|
||||
for (let box of boxes) {
|
||||
await this.imap.openBox(box.path);
|
||||
const meta = await this.imap.fetchMessages('1:*');
|
||||
Object.keys(meta).forEach(id => allMeta.push(meta[id]));
|
||||
}
|
||||
|
||||
allMeta = allMeta.sort((a, b) => +a.date - +b.date);
|
||||
return allMeta;
|
||||
}
|
||||
|
||||
private createConversations(messages: RawMessage[]): Conversation[] {
|
||||
const conversations: Conversation[] = [];
|
||||
|
||||
messages.forEach(message => {
|
||||
if (message.replyTo) {
|
||||
for (let conversation of conversations) {
|
||||
for (let reference of [ ...message.references, message.replyTo ]) {
|
||||
if (conversation.messages.has(reference)) {
|
||||
conversation.date = message.date;
|
||||
conversation.messages.add(message.messageId);
|
||||
conversation.title = this.cleanSubjectLine(message.subject);
|
||||
conversation.active = conversation.active || message.active;
|
||||
message.to.forEach(p => conversation.participants.add(p.address));
|
||||
conversation.participants.add(message.from.address);
|
||||
return;
|
||||
}
|
||||
}
|
||||
async synchronizeMessages(remoteBoxes: Map<string, Imap.Box>): Promise<void> {
|
||||
const currentBoxes = await DB.MailboxModel.find({ account: this.accountID });
|
||||
await Promise.all(currentBoxes.map(async box => {
|
||||
const remote = remoteBoxes.get(box.path)!;
|
||||
console.log(box.uidNext, remote.uidnext);
|
||||
// if (box.uidValidity !== remote.uidvalidity) {
|
||||
// // Reacquire existing messages
|
||||
// }
|
||||
if (box.uidNext !== remote.uidnext) {
|
||||
// Get new messages
|
||||
const messages = await (await this.conn.get(box.path)).fetchMessagesByUID(`${box.uidNext}:*`);
|
||||
await DB.MailboxModel.updateOne({ _id: box._id }, { uidNext: remote.uidnext });
|
||||
if (messages.size > 0) {
|
||||
console.log('adding ' + messages.size + ' messages.');
|
||||
await DB.MessageModel.insertMany([ ...messages.keys() ].map(uid => {
|
||||
const message = messages.get(uid)!;
|
||||
const headers = this.parseHeaders(message.headers);
|
||||
return {
|
||||
account: this.accountID,
|
||||
box: box._id,
|
||||
uid: uid,
|
||||
messageId: headers.get('MESSAGE-ID') ?? '[!DATE:' + (+message.attrs.date) + ']',
|
||||
subject: this.cleanSubject(headers.get('SUBJECT')),
|
||||
date: message.attrs.date,
|
||||
} as DB.Message;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
conversations.push({
|
||||
title: this.cleanSubjectLine(message.subject),
|
||||
messages: new Set([ message.messageId ]),
|
||||
date: message.date,
|
||||
active: message.active,
|
||||
participants: new Set([ message.from.address, ...message.to.map(p => p.address) ])
|
||||
});
|
||||
});
|
||||
|
||||
conversations.forEach(conversation => {
|
||||
conversation.participants.delete(this.address);
|
||||
});
|
||||
|
||||
return conversations.sort((a, b) => +a.date - +b.date);
|
||||
}));
|
||||
}
|
||||
|
||||
private createContacts(messages: RawMessage[]): Contact[] {
|
||||
const contacts: Contact[] = [];
|
||||
private async getBoxes(): Promise<Map<string, Imap.Box>> {
|
||||
Log.perfStart('Getting boxes for ' + this.address);
|
||||
|
||||
for (let message of messages) {
|
||||
[ message.from, ...message.to ].forEach(participant => {
|
||||
for (let contact of contacts) {
|
||||
if (contact.addresses.has(participant.address)) {
|
||||
if (participant.name) contact.name = participant.name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const remoteBoxes = await (await this.conn.get()).getBoxes();
|
||||
const boxReqs: Promise<void>[] = [];
|
||||
const boxes: Map<string, Imap.Box> = new Map();
|
||||
|
||||
contacts.push({
|
||||
name: participant.name ?? participant.address,
|
||||
addresses: new Set([ participant.address ])
|
||||
});
|
||||
const reqBoxesRecursively = (tree: Imap.MailBoxes, path: string = '') => {
|
||||
Object.keys(tree).forEach(name => {
|
||||
boxReqs.push((async () => {
|
||||
const box = (await this.conn.get(path + name)).getOpenBoxProps();
|
||||
boxes.set(path + name, box);
|
||||
})());
|
||||
if (tree[name].children) reqBoxesRecursively(tree[name].children,
|
||||
path + name + tree[name].delimiter);
|
||||
});
|
||||
}
|
||||
|
||||
return contacts;
|
||||
reqBoxesRecursively(remoteBoxes);
|
||||
await Promise.all(boxReqs);
|
||||
|
||||
Log.perfEnd('Getting boxes for ' + this.address);
|
||||
return boxes;
|
||||
}
|
||||
|
||||
private cleanSubjectLine(subject: string = '') {
|
||||
private parseHeaders(rawHeaders: string): Map<string, string> {
|
||||
const headers: Map<string, string> = new Map();
|
||||
rawHeaders
|
||||
.split(/\r?\n(?=[A-z:\-_]+)/g)
|
||||
.map(h => h.trim())
|
||||
.filter(h => h)
|
||||
.forEach(h => {
|
||||
const delimiter = h.indexOf(':');
|
||||
const name = h.substr(0, delimiter).trim();
|
||||
const value = h.substr(delimiter + 1).trim();
|
||||
headers.set(name.toUpperCase(), value);
|
||||
});
|
||||
return headers;
|
||||
}
|
||||
|
||||
private cleanSubject(subject: string = '') {
|
||||
return subject.replace(/^((re|fwd?|b?cc)(:| ) *)*/gi, '').trim();
|
||||
}
|
||||
|
||||
// async synchronizeData(): Promise<void> {
|
||||
// const existingBoxes = await DB.MailboxModel.find({ account: this.data._id });
|
||||
// const remoteBoxes = (await this.imap.listBoxes());
|
||||
|
||||
// const newBoxes: (Mailbox & { uidValidity: number })[] = [];
|
||||
// const boxValidityChanged: { _id: ObjectID, uidValidity: number }[] = [];
|
||||
// const removedBoxes: Set<ObjectID> = new Set(existingBoxes.map(box => box._id));
|
||||
|
||||
// for (let remote of remoteBoxes) {
|
||||
// if (!remote.treeTypes.has(DB.MailboxType.Inbox) &&
|
||||
// !remote.treeTypes.has(DB.MailboxType.Sent) &&
|
||||
// !remote.treeTypes.has(DB.MailboxType.Archives)) continue;
|
||||
|
||||
// const existing = existingBoxes.filter(box => box.path === remote.path)[0];
|
||||
// let uidValidity = (await this.imap.openBox(remote.path)).uidvalidity;
|
||||
// // Log.debug('Opened %s', remote.path);
|
||||
// if (!existing) newBoxes.push({ ...remote, uidValidity });
|
||||
// else {
|
||||
// removedBoxes.delete(existing._id);
|
||||
// if (existing.uidValidity != uidValidity) boxValidityChanged.push({ _id: existing._id, uidValidity });
|
||||
// }
|
||||
// };
|
||||
|
||||
// let createdIDs: Map<string, ObjectID> = new Map();
|
||||
|
||||
// for (let box of newBoxes) {
|
||||
// createdIDs.set(box.path, (await DB.MailboxModel.create({
|
||||
// name: box.name,
|
||||
// path: box.path,
|
||||
// account: this.data._id,
|
||||
// delimiter: box.delimiter,
|
||||
// type: box.type,
|
||||
// treeTypes: box.treeTypes,
|
||||
// parent: (await DB.MailboxModel.findOne({ path: box.parent }))?._id,
|
||||
// uidValidity: box.uidValidity,
|
||||
// uidNext: 1,
|
||||
// } as DB.Create<DB.Mailbox>)).id);
|
||||
// }
|
||||
|
||||
// await Promise.all(boxValidityChanged.map(({ _id, uidValidity }) =>
|
||||
// DB.MailboxModel.updateOne({ _id }, { uidValidity, uidNext: 1 })));
|
||||
|
||||
// await DB.MailboxModel.deleteMany({ _id: { $in: [ ...removedBoxes ] } });
|
||||
|
||||
// for (let box of await DB.MailboxModel.find({ account: this.data._id })) {
|
||||
// await this.imap.openBox(box.path);
|
||||
// const meta = Object.values(await this.imap.fetchMessages(box.uidNext + ':*'));
|
||||
|
||||
// const contacts: { name: string; addresses: Set<string> }[] = [];
|
||||
|
||||
// for (let m of meta) {
|
||||
// [ m.from, ...m.to ].forEach(participant => {
|
||||
// for (let contact of contacts) {
|
||||
// if (contact.addresses.has(participant.address)) {
|
||||
// if (participant.name) contact.name = participant.name;
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// contacts.push({
|
||||
// name: participant.name ?? participant.address,
|
||||
// addresses: new Set([ participant.address ])
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// await Promise.all(contacts.map(async contact => {
|
||||
// const addresses = [ ...contact.addresses ];
|
||||
// await DB.ContactModel.updateOne(
|
||||
// { addresses: { $elemMatch: { $in: addresses } as any } },
|
||||
// {
|
||||
// $set: { name: contact.name },
|
||||
// $addToSet: { addresses }
|
||||
// },
|
||||
// { upsert: true });
|
||||
// }));
|
||||
|
||||
// await Promise.all(meta.map(async meta => {
|
||||
// await DB.MessageModel.updateOne({ messageId: meta.messageId }, {
|
||||
// box: box._id,
|
||||
// uid: meta.boxId,
|
||||
// account: this.data._id,
|
||||
// $setOnInsert: {
|
||||
// subject: meta.subject,
|
||||
// date: meta.date,
|
||||
// from: (await DB.ContactModel.findOne({ addresses: meta.from.address }))!._id
|
||||
// }
|
||||
// } as any as DB.Message,
|
||||
// { upsert: true });
|
||||
// }));
|
||||
|
||||
// // await DB.MessageIDModel.insertMany(Object.values(meta).map(meta =>
|
||||
// // ({ messageId: meta.messageId, uid: meta.boxId, account: this.data._id, box: box._id })));
|
||||
// }
|
||||
|
||||
// const boxes = (await this.imap.listBoxes()).filter(box =>
|
||||
// !box.treeTypes.has(MailboxType.Spam) && !box.treeTypes.has(MailboxType.Trash));
|
||||
|
||||
// let allMeta: RawMessage[] = [];
|
||||
|
||||
// for (let box of boxes) {
|
||||
// await this.imap.openBox(box.path);
|
||||
// const meta = await this.imap.fetchMessages('1:*');
|
||||
// Object.keys(meta).forEach(id => allMeta.push(meta[id]));
|
||||
// }
|
||||
// }
|
||||
|
||||
// getName() {
|
||||
// return this.name;
|
||||
// }
|
||||
|
||||
// getAddress() {
|
||||
// return this.address;
|
||||
// }
|
||||
|
||||
// getImage() {
|
||||
// return this.image;
|
||||
// }
|
||||
|
||||
// hasUnreads() {
|
||||
// return this.unread;
|
||||
// }
|
||||
|
||||
// getConversations() {
|
||||
// return this.conversations;
|
||||
// }
|
||||
|
||||
// getContacts() {
|
||||
// return this.contacts;
|
||||
// }
|
||||
|
||||
// getMessages(_messages: string[]): Message[] {
|
||||
// return [{
|
||||
// id: 'AOUEOAEu',
|
||||
// date: new Date(),
|
||||
// from: 'me@auri.xyz',
|
||||
// to: [ 'nicole@aurailus.design' ],
|
||||
// content: '<p>Lorem ipsum dolor sit amet.</p>'
|
||||
// }];
|
||||
// }
|
||||
|
||||
// async fetchAllMessages(): Promise<RawMessage[]> {
|
||||
// const boxes = (await this.imap.listBoxes()).filter(box =>
|
||||
// !box.treeTypes.has(MailboxType.Spam) && !box.treeTypes.has(MailboxType.Trash));
|
||||
|
||||
// let allMeta: RawMessage[] = [];
|
||||
|
||||
// for (let box of boxes) {
|
||||
// await this.imap.openBox(box.path);
|
||||
// const meta = await this.imap.fetchMessages('1:*');
|
||||
// Object.keys(meta).forEach(id => allMeta.push(meta[id]));
|
||||
// }
|
||||
|
||||
// allMeta = allMeta.sort((a, b) => +a.date - +b.date);
|
||||
// return allMeta;
|
||||
// }
|
||||
|
||||
// private createConversations(messages: RawMessage[]): Conversation[] {
|
||||
// const conversations: Conversation[] = [];
|
||||
|
||||
// messages.forEach(message => {
|
||||
// if (message.replyTo) {
|
||||
// for (let conversation of conversations) {
|
||||
// for (let reference of [ ...message.references, message.replyTo ]) {
|
||||
// if (conversation.messages.has(reference)) {
|
||||
// conversation.date = message.date;
|
||||
// conversation.messages.add(message.messageId);
|
||||
// conversation.title = this.cleanSubjectLine(message.subject);
|
||||
// conversation.active = conversation.active || message.active;
|
||||
// message.to.forEach(p => conversation.participants.add(p.address));
|
||||
// conversation.participants.add(message.from.address);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// conversations.push({
|
||||
// title: this.cleanSubjectLine(message.subject),
|
||||
// messages: new Set([ message.messageId ]),
|
||||
// date: message.date,
|
||||
// active: message.active,
|
||||
// participants: new Set([ message.from.address, ...message.to.map(p => p.address) ])
|
||||
// });
|
||||
// });
|
||||
|
||||
// conversations.forEach(conversation => {
|
||||
// conversation.participants.delete(this.address);
|
||||
// });
|
||||
|
||||
// return conversations.sort((a, b) => +a.date - +b.date);
|
||||
// }
|
||||
|
||||
// private createContacts(messages: RawMessage[]): Contact[] {
|
||||
// const contacts: Contact[] = [];
|
||||
|
||||
// for (let message of messages) {
|
||||
// [ message.from, ...message.to ].forEach(participant => {
|
||||
// for (let contact of contacts) {
|
||||
// if (contact.addresses.has(participant.address)) {
|
||||
// if (participant.name) contact.name = participant.name;
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// contacts.push({
|
||||
// name: participant.name ?? participant.address,
|
||||
// addresses: new Set([ participant.address ])
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// return contacts;
|
||||
// }
|
||||
|
||||
};
|
||||
|
|
|
@ -1,61 +1,61 @@
|
|||
import { buildSchema } from 'graphql';
|
||||
// import { buildSchema } from 'graphql';
|
||||
|
||||
import { Type, SCHEMA } from 'common/graph';
|
||||
// import { Type, SCHEMA } from 'common/graph';
|
||||
|
||||
import Message from './Message';
|
||||
import Account from './Account';
|
||||
import Conversation from './Conversation';
|
||||
// import Message from './Message';
|
||||
// import Account from './Account';
|
||||
// import Conversation from './Conversation';
|
||||
|
||||
export interface Context {
|
||||
accounts: Record<Type.ID, Account>;
|
||||
}
|
||||
// export interface Context {
|
||||
// accounts: Record<Type.ID, Account>;
|
||||
// }
|
||||
|
||||
export const Schema = buildSchema(SCHEMA);
|
||||
// export const Schema = buildSchema(SCHEMA);
|
||||
|
||||
function messageResolver(message: Message) {
|
||||
return {
|
||||
id: message.id,
|
||||
date: message.date,
|
||||
from: message.from,
|
||||
to: message.to,
|
||||
// function messageResolver(message: Message) {
|
||||
// return {
|
||||
// id: message.id,
|
||||
// date: message.date,
|
||||
// from: message.from,
|
||||
// to: message.to,
|
||||
|
||||
html: () => message.content,
|
||||
markdown: () => message.content
|
||||
};
|
||||
}
|
||||
// html: () => message.content,
|
||||
// markdown: () => message.content
|
||||
// };
|
||||
// }
|
||||
|
||||
function conversationResolver(conversation: Conversation, id: string) {
|
||||
return {
|
||||
id: id,
|
||||
unread: false,
|
||||
title: conversation.title,
|
||||
lastMessage: conversation.date,
|
||||
messages: conversation.messages,
|
||||
participants: conversation.participants
|
||||
};
|
||||
}
|
||||
// function conversationResolver(conversation: Conversation, id: string) {
|
||||
// return {
|
||||
// id: id,
|
||||
// unread: false,
|
||||
// title: conversation.title,
|
||||
// lastMessage: conversation.date,
|
||||
// messages: conversation.messages,
|
||||
// participants: conversation.participants
|
||||
// };
|
||||
// }
|
||||
|
||||
function accountResolver(account: Account, id: string) {
|
||||
return {
|
||||
id: id,
|
||||
name: account.getName(),
|
||||
image: account.getImage(),
|
||||
address: account.getAddress(),
|
||||
unread: account.hasUnreads(),
|
||||
// function accountResolver(account: Account, id: string) {
|
||||
// return {
|
||||
// id: id,
|
||||
// name: account.getName(),
|
||||
// image: account.getImage(),
|
||||
// address: account.getAddress(),
|
||||
// unread: account.hasUnreads(),
|
||||
|
||||
messages: [],
|
||||
contacts: () => account.getContacts(),
|
||||
conversations: () => {
|
||||
const conversations = account.getConversations();
|
||||
for (let key in conversations) if (!conversations[key].active) delete conversations[key];
|
||||
return Object.keys(conversations).map(id => conversationResolver(conversations[id as any], id));
|
||||
}
|
||||
};
|
||||
}
|
||||
// messages: [],
|
||||
// contacts: () => account.getContacts(),
|
||||
// conversations: () => {
|
||||
// const conversations = account.getConversations();
|
||||
// for (let key in conversations) if (!conversations[key].active) delete conversations[key];
|
||||
// return Object.keys(conversations).map(id => conversationResolver(conversations[id as any], id));
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
export const Resolver = {
|
||||
accounts: (_: any, ctx: Context) => Object.keys(ctx.accounts).map(id => accountResolver(ctx.accounts[id], id)),
|
||||
account: ({ account: id }: { account: string }, ctx: Context) => accountResolver(ctx.accounts[id], id),
|
||||
messages: async ({ account, ids }: { account: string; ids: string[] }, ctx: Context) =>
|
||||
(await ctx.accounts[account].getMessages(ids)).map(msg => messageResolver(msg))
|
||||
};
|
||||
// export const Resolver = {
|
||||
// accounts: (_: any, ctx: Context) => Object.keys(ctx.accounts).map(id => accountResolver(ctx.accounts[id], id)),
|
||||
// account: ({ account: id }: { account: string }, ctx: Context) => accountResolver(ctx.accounts[id], id),
|
||||
// messages: async ({ account, ids }: { account: string; ids: string[] }, ctx: Context) =>
|
||||
// (await ctx.accounts[account].getMessages(ids)).map(msg => messageResolver(msg))
|
||||
// };
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import md5 from 'md5';
|
||||
|
||||
import RawImap, { MailBoxes as RawBoxes } from 'imap';
|
||||
import { MailboxType } from './data/Data';
|
||||
import RawImap, { MailBoxes as RawBoxes, Box as RawBox } from 'imap';
|
||||
|
||||
/** Credentials and properties used to establish an IMAP connection. */
|
||||
|
||||
|
@ -12,27 +13,6 @@ export interface ConnectionProperties {
|
|||
tls: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mailbox types (attributes).
|
||||
* Only the attributes relevant to Aether are included,
|
||||
* and some are named differently to better match interface language.
|
||||
*/
|
||||
|
||||
export enum MailboxType {
|
||||
Box = 'NORMAL_BOX',
|
||||
All = '\\All',
|
||||
Archive = '\\Archive',
|
||||
Drafts = '\\Drafts',
|
||||
Starred = '\\Flagged',
|
||||
Important = '\\Important',
|
||||
Inbox = '\\Inbox',
|
||||
Spam = '\\Junk',
|
||||
Sent = '\\Sent',
|
||||
Trash = '\\Trash'
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Message flags (keywords).
|
||||
* Only the flags relevant to Aether are included,
|
||||
|
@ -106,7 +86,7 @@ export interface Message {
|
|||
*/
|
||||
|
||||
export default class Imap {
|
||||
private raw: RawImap;
|
||||
raw: RawImap;
|
||||
private connected: boolean = false;
|
||||
|
||||
private boxes: Mailbox[] = [];
|
||||
|
@ -189,9 +169,11 @@ export default class Imap {
|
|||
for (let boxName in boxes) {
|
||||
if ({}.hasOwnProperty.call(boxes, boxName)) {
|
||||
const box = boxes[boxName];
|
||||
|
||||
const thisPath = path + boxName + box.delimiter;
|
||||
const type = (box as any).special_use_attrib as MailboxType ??
|
||||
(boxName === 'INBOX' ? MailboxType.Inbox : MailboxType.Box);
|
||||
(boxName === 'INBOX' ? MailboxType.Inbox :
|
||||
boxName.match(/^Archives?$/gi) !== null ? MailboxType.Archives : MailboxType.Box);
|
||||
const thisTreeTypes = new Set([ ...treeTypes, type ]);
|
||||
|
||||
foundBoxes.push({
|
||||
|
@ -223,7 +205,7 @@ export default class Imap {
|
|||
* @returns a raw node-imap box instance.
|
||||
*/
|
||||
|
||||
openBox(box: string): Promise<Mailbox> {
|
||||
openBox(box: string): Promise<RawBox> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.connected) reject('Cannot get box when the connection is closed.');
|
||||
|
||||
|
@ -233,8 +215,9 @@ export default class Imap {
|
|||
return;
|
||||
}
|
||||
|
||||
resolve(box);
|
||||
this.box = this.boxes.filter(b => b.path === box.name)[0];
|
||||
resolve(this.box);
|
||||
// resolve(this.box);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -269,8 +252,9 @@ export default class Imap {
|
|||
const id = parseInt(idStr, 10);
|
||||
const headers = bodies[id].headers;
|
||||
const attrs = bodies[id].attrs;
|
||||
const messageId = headers['MESSAGE-ID'] || `HASH:${md5(attrs.date.toString())}:${md5(headers.SUBJECT)}`;
|
||||
|
||||
messages[headers['MESSAGE-ID']] = {
|
||||
messages[messageId] = {
|
||||
to: this.parseParticipants(headers.TO ?? ''),
|
||||
from: this.parseParticipants(headers.FROM ?? '')[0],
|
||||
subject: headers.SUBJECT,
|
||||
|
@ -278,7 +262,7 @@ export default class Imap {
|
|||
|
||||
id: id,
|
||||
boxId: attrs.uid,
|
||||
messageId: headers['MESSAGE-ID'] || `HASH:${md5(attrs.date.toString())}:${md5(headers.SUBJECT)}`,
|
||||
messageId: messageId,
|
||||
active: attrs.flags.has(MessageFlag.Active) || this.box!.type === MailboxType.Inbox,
|
||||
|
||||
replyTo: headers['IN-REPLY-TO'],
|
||||
|
@ -364,10 +348,10 @@ export default class Imap {
|
|||
private parseParticipants(header: string): Participant[] {
|
||||
return header.split(',').map(raw => {
|
||||
const delimiter = raw.indexOf('<');
|
||||
if (delimiter === -1) return { name: undefined, address: raw.trim() };
|
||||
if (delimiter === -1) return { name: undefined, address: raw.trim().toLowerCase() };
|
||||
|
||||
const name = raw.substr(0, delimiter).replace(/^[\s'"]+/g, '').trim().replace(/[\s'"]+$/g, '');
|
||||
const address = raw.substr(delimiter + 1).replace(/[<>]/g, '').replace(/^[\s'"]+/g, '').trim().replace(/[\s'"]+$/g, '');
|
||||
const address = raw.substr(delimiter + 1).replace(/[<>]/g, '').replace(/^[\s'"]+/g, '').trim().replace(/[\s'"]+$/g, '').toLowerCase();
|
||||
|
||||
return { name: name ? name : undefined, address };
|
||||
});
|
||||
|
|
|
@ -20,17 +20,17 @@ log4js.configure({
|
|||
|
||||
const logger = log4js.getLogger();
|
||||
|
||||
const activePerfs: Record<string, [ number, number ]> = {};
|
||||
const activePerfs: Record<string, bigint> = {};
|
||||
|
||||
const perfStart = (identifier: string) => {
|
||||
activePerfs[identifier] = process.hrtime();
|
||||
activePerfs[identifier] = process.hrtime.bigint();
|
||||
};
|
||||
|
||||
const perfEnd = (identifier: string) => {
|
||||
let perf = activePerfs[identifier];
|
||||
if (!perf) logger.warn('Attempted to perf invalid identifier \'%s\'.', identifier);
|
||||
let start = activePerfs[identifier];
|
||||
if (!start) logger.warn('Attempted to perf invalid identifier \'%s\'.', identifier);
|
||||
else {
|
||||
const elapsed = process.hrtime(perf)[1] / 1000000;
|
||||
const elapsed = Number((process.hrtime.bigint() - start) / BigInt(10000)) / 100;
|
||||
// @ts-ignore
|
||||
logger.perf('%s took %s ms.', identifier, elapsed.toFixed(3));
|
||||
delete activePerfs[identifier];
|
||||
|
|
|
@ -1,38 +1,64 @@
|
|||
import fs from 'fs';
|
||||
import { graphql } from 'graphql';
|
||||
// import fs from 'fs';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
// import { graphql } from 'graphql';
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
import Log from './Log';
|
||||
import Account from './Account';
|
||||
import { Schema, Resolver } from './Graph';
|
||||
import { openWindow } from './ElectronWindow';
|
||||
import * as DB from './data/Data';
|
||||
// import { Schema, Resolver } from './Graph';
|
||||
// import { openWindow } from './ElectronWindow';
|
||||
|
||||
Log.setLogLevel('debug');
|
||||
Log.setLogLevel('all');
|
||||
|
||||
(async () => {
|
||||
const accounts: Record<string, Account> = {
|
||||
'0': new Account('Personal', '../../client/res/user-home.png', {
|
||||
username: 'me@auri.xyz',
|
||||
password: fs.readFileSync(__dirname + '/../../client/pw.txt').toString().trim(),
|
||||
host: 'mail.hover.com',
|
||||
port: 993,
|
||||
tls: true
|
||||
}),
|
||||
'1': new Account('Work', '../../client/res/user-work.png', {
|
||||
username: 'nicole@aurailus.design',
|
||||
password: fs.readFileSync(__dirname + '/../../client/pw.txt').toString().trim(),
|
||||
host: 'mail.hover.com',
|
||||
port: 993,
|
||||
tls: true
|
||||
})
|
||||
};
|
||||
// openWindow();
|
||||
Log.info('Initialized.');
|
||||
|
||||
await Promise.all(Object.values(accounts).map(account => account.connect()));
|
||||
await mongoose.connect('mongodb://localhost:27017/aether',{
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
useCreateIndex: true,
|
||||
useFindAndModify: true
|
||||
} as mongoose.ConnectOptions);
|
||||
|
||||
ipcMain.handle('graphql', async (_, req: { query: string; data: any }) => {
|
||||
return graphql(Schema, req.query, Resolver, { accounts }, req.data);
|
||||
Log.info('Connected to Mongoose.');
|
||||
|
||||
// await DB.AccountModel.deleteMany({});
|
||||
// await DB.AccountModel.insertMany([{
|
||||
// name: 'Personal',
|
||||
// image: '../../client/res/user-home.png',
|
||||
// address: 'me@auri.xyz',
|
||||
// password: fs.readFileSync(__dirname + '/../../client/pw.txt').toString().trim(),
|
||||
// host: 'mail.hover.com',
|
||||
// port: 993,
|
||||
// tls: true
|
||||
// }, {
|
||||
// name: 'Work',
|
||||
// image: '../../client/res/user-work.png',
|
||||
// address: 'nicole@aurailus.design',
|
||||
// password: fs.readFileSync(__dirname + '/../../client/pw.txt').toString().trim(),
|
||||
// host: 'mail.hover.com',
|
||||
// port: '993',
|
||||
// tls: true
|
||||
// }] as DB.Create<DB.Account>[]);
|
||||
|
||||
Log.perfStart('Initial DB Fetch');
|
||||
const dbAccounts = (await DB.AccountModel.find({}))!;
|
||||
Log.perfEnd('Initial DB Fetch');
|
||||
|
||||
Log.perfStart('Accounts Connect');
|
||||
await Promise.all(dbAccounts.map(async dbAccount => {
|
||||
const account = new Account(dbAccount);
|
||||
await account.init();
|
||||
return account;
|
||||
}));
|
||||
Log.perfEnd('Accounts Connect');
|
||||
|
||||
ipcMain.handle('graphql', async (_, _req: { query: string; data: any }) => {
|
||||
return { data: { } };
|
||||
});
|
||||
|
||||
openWindow();
|
||||
// return graphql(Schema, req.query, Resolver, { accounts }, req.data);
|
||||
// });
|
||||
})();
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export default interface Message {
|
||||
id: string;
|
||||
|
||||
|
||||
date: Date;
|
||||
from: string;
|
||||
to: string[];
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
import { ObjectID } from 'mongodb';
|
||||
import { prop, index, Ref, getModelForClass, modelOptions } from '@typegoose/typegoose';
|
||||
|
||||
export type Create<T> = Omit<T, 'id' | '_id'>;
|
||||
|
||||
export enum MailboxType {
|
||||
Box = 'NORMAL_BOX',
|
||||
Archives = 'ARCHIVES',
|
||||
All = '\\All',
|
||||
Drafts = '\\Drafts',
|
||||
Starred = '\\Flagged',
|
||||
Important = '\\Important',
|
||||
Inbox = '\\Inbox',
|
||||
Spam = '\\Junk',
|
||||
Sent = '\\Sent',
|
||||
Trash = '\\Trash'
|
||||
};
|
||||
|
||||
@modelOptions({ schemaOptions: { versionKey: false }})
|
||||
@index({ address: 1 })
|
||||
export class Account {
|
||||
id!: string;
|
||||
_id!: ObjectID;
|
||||
|
||||
@prop({ required: true })
|
||||
name!: string;
|
||||
|
||||
@prop()
|
||||
image?: string;
|
||||
|
||||
@prop({ required: true })
|
||||
address!: string;
|
||||
|
||||
@prop({ required: true })
|
||||
password!: string;
|
||||
|
||||
@prop({ required: true })
|
||||
host!: string;
|
||||
|
||||
@prop({ required: true })
|
||||
port!: number;
|
||||
|
||||
@prop({ default: true })
|
||||
tls!: boolean;
|
||||
};
|
||||
|
||||
export const AccountModel = getModelForClass(Account);
|
||||
|
||||
@modelOptions({ schemaOptions: { versionKey: false }})
|
||||
@index({ account: 1, path: 1 })
|
||||
export class Mailbox {
|
||||
id!: string;
|
||||
_id!: ObjectID;
|
||||
|
||||
@prop({ required: true, ref: Account })
|
||||
account!: Ref<Account>;
|
||||
|
||||
@prop({ required: true })
|
||||
name!: string;
|
||||
|
||||
@prop({ required: true })
|
||||
path!: string;
|
||||
|
||||
@prop({ required: true })
|
||||
delimiter!: string;
|
||||
|
||||
@prop({ required: true })
|
||||
type!: MailboxType;
|
||||
|
||||
@prop({ required: true, type: [String] })
|
||||
private _treeTypes!: MailboxType[];
|
||||
|
||||
get treeTypes(): Set<MailboxType> { return new Set(this._treeTypes); }
|
||||
set treeTypes(treeTypes: Set<MailboxType>) { this._treeTypes = [ ...treeTypes ]; }
|
||||
|
||||
@prop({ ref: Mailbox })
|
||||
parent?: Ref<Mailbox>;
|
||||
|
||||
@prop({ required: true })
|
||||
uidValidity!: number;
|
||||
|
||||
@prop({ required: true })
|
||||
uidNext!: number;
|
||||
};
|
||||
|
||||
export const MailboxModel = getModelForClass(Mailbox);
|
||||
|
||||
@modelOptions({ schemaOptions: { versionKey: false }})
|
||||
export class Contact {
|
||||
id!: string;
|
||||
_id!: ObjectID;
|
||||
|
||||
@prop({ default: false })
|
||||
userCreated?: boolean;
|
||||
|
||||
@prop({ required: true })
|
||||
name!: string;
|
||||
|
||||
@prop({ default: [], type: [String]})
|
||||
addresses?: string[];
|
||||
};
|
||||
|
||||
export const ContactModel = getModelForClass(Contact);
|
||||
|
||||
@modelOptions({ schemaOptions: { versionKey: false }})
|
||||
@index({ account: 1, messageId: 1 })
|
||||
@index({ account: 1, box: 1, uid: 1 })
|
||||
export class Message {
|
||||
id!: string;
|
||||
_id!: ObjectID;
|
||||
|
||||
@prop({ required: true, ref: Account })
|
||||
account!: Ref<Account>;
|
||||
|
||||
@prop({ required: true, ref: Mailbox })
|
||||
box!: Ref<Mailbox>;
|
||||
|
||||
@prop({ required: true })
|
||||
uid!: number;
|
||||
|
||||
@prop({ required: true })
|
||||
messageId!: string;
|
||||
|
||||
@prop({ required: true })
|
||||
subject!: string;
|
||||
|
||||
@prop({ required: true })
|
||||
date!: Date;
|
||||
|
||||
// @prop({ required: true, ref: Contact })
|
||||
// from!: Ref<Contact>;
|
||||
|
||||
// @prop({ required: true, ref: Contact })
|
||||
// to!: Ref<Contact>[];
|
||||
};
|
||||
|
||||
export const MessageModel = getModelForClass(Message);
|
|
@ -0,0 +1,233 @@
|
|||
import Imap from 'imap';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
export enum FetchMode { SEQ, UID };
|
||||
|
||||
export type FetchSpecifier = string | string[] | number | number[];
|
||||
|
||||
export interface FetchMessage {
|
||||
headers: string;
|
||||
attrs: Imap.ImapMessageAttributes;
|
||||
}
|
||||
|
||||
export const FETCH_DEFAULT_BODIES = 'HEADER.FIELDS (FROM TO SUBJECT DATE MESSAGE-ID)';
|
||||
|
||||
export interface ConnectionProperties {
|
||||
user: string;
|
||||
password: string;
|
||||
host: string;
|
||||
port: number;
|
||||
tls: boolean;
|
||||
}
|
||||
|
||||
export default class ImapConnection {
|
||||
readonly event: EventEmitter = new EventEmitter();
|
||||
|
||||
private conn: Imap;
|
||||
private connected: boolean = false;
|
||||
private currentBox: string | null = null;
|
||||
private currentBoxProps: Imap.Box | null = null;
|
||||
|
||||
private operationNext: number = 0;
|
||||
private operationsPending: Set<number> = new Set();
|
||||
|
||||
constructor(connectionProps: ConnectionProperties) {
|
||||
this.conn = new Imap(connectionProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the connection is idle.
|
||||
*
|
||||
* @returns a boolean indicating if the connection is idle.
|
||||
*/
|
||||
|
||||
isIdle(): boolean {
|
||||
if (!this.connected) throw new Error('Attempted to check if an unconnected connection is idle.');
|
||||
return this.operationsPending.size === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks and executes the provided function to prevent disconnection
|
||||
* or box changes occuring while it is in progress. All operations that
|
||||
* need to access the open box on the remote server should be wrapped
|
||||
* by this function. Propagates the returned values or errors up.
|
||||
*
|
||||
* @param fn - The function to execute.
|
||||
* @returns the function's return value.
|
||||
*/
|
||||
|
||||
private executeOperation<T = any>(fn: () => Promise<T>): Promise<T> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!this.connected) reject(new Error(
|
||||
'Attempted to perform an operation on an unconnected connection.'));
|
||||
|
||||
const operation = this.operationNext++;
|
||||
this.operationsPending.add(operation);
|
||||
|
||||
try {
|
||||
this.operationsPending.delete(operation);
|
||||
resolve(await fn());
|
||||
if (this.isIdle()) setTimeout(() => this.event.emit('idle'), 0);
|
||||
}
|
||||
catch (e: unknown) {
|
||||
this.operationsPending.delete(operation);
|
||||
reject(e);
|
||||
if (this.isIdle()) setTimeout(() => this.event.emit('idle'), 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the Imap connection, resolves when complete.
|
||||
*
|
||||
* @returns a promise that resolves upon connection or rejects with an error.
|
||||
*/
|
||||
|
||||
connect(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.connected) {
|
||||
reject(new Error('Attempted to connect while already connected.'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.conn.once('end', () => this.connected = false);
|
||||
this.conn.once('error', (error: any) => reject(error));
|
||||
this.conn.once('ready', async () => {
|
||||
this.connected = true;
|
||||
resolve();
|
||||
});
|
||||
|
||||
this.conn.connect();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the connection is connected.
|
||||
*
|
||||
* @returns a boolean indicating if there is an active connection.
|
||||
*/
|
||||
|
||||
isConnected(): boolean {
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the specified box (safely). Throws if there are pending operations.
|
||||
*
|
||||
* @param path - The path of the box to open.
|
||||
* @returns the box that was opened.
|
||||
*/
|
||||
|
||||
async openBox(path: string): Promise<Imap.Box> {
|
||||
return this.executeOperation(() => new Promise((resolve, reject) => {
|
||||
if (!this.connected) reject(new Error('Tried to open a box while not connected.'));
|
||||
if (!this.isIdle()) reject(new Error('Tried to change box while the connection was not idle.'));
|
||||
|
||||
this.currentBox = null;
|
||||
this.conn.openBox(path, false, (err, box) => {
|
||||
if (err) reject(err);
|
||||
else {
|
||||
this.currentBox = path;
|
||||
this.currentBoxProps = JSON.parse(JSON.stringify(box));
|
||||
resolve(this.currentBoxProps!);
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the currently open box, if one is open.
|
||||
*
|
||||
* @returns the name of the box that is open, or null if none are open.
|
||||
*/
|
||||
|
||||
getOpenBox(): string | null {
|
||||
return this.currentBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the properties of the currently open box, or throws if there isn't one open.
|
||||
*
|
||||
* @returns an Imap.Box for the currently open box.
|
||||
*/
|
||||
|
||||
getOpenBoxProps(): Imap.Box {
|
||||
if (!this.currentBoxProps) throw new Error('Tried to get box props when there wasn\'t an open box.');
|
||||
return this.currentBoxProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a tree of boxes on the server.
|
||||
*
|
||||
* @returns a tree of imap mailboxes.
|
||||
*/
|
||||
|
||||
getBoxes(): Promise<Imap.MailBoxes> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.conn.getBoxes((err, boxes) => {
|
||||
if (err) reject(err);
|
||||
else resolve(boxes);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches messages either by SeqNo or by UID and returns their raw headers and attributes.
|
||||
*
|
||||
* @param mode - The mode to fetch with.
|
||||
* @param query - The query to send to the server.
|
||||
* @param bodies - The header bodies to fetch.
|
||||
* @returns a map of messages indexed by UID or SeqNo.
|
||||
*/
|
||||
|
||||
async fetchMessages(mode: FetchMode, query: FetchSpecifier,
|
||||
bodies: string = FETCH_DEFAULT_BODIES): Promise<Map<number, FetchMessage>> {
|
||||
|
||||
return this.executeOperation(() => new Promise((resolve, reject) => {
|
||||
const fetchRoot = mode === FetchMode.SEQ ? this.conn.seq : this.conn;
|
||||
|
||||
const messages: Map<number, FetchMessage> = new Map();
|
||||
const fetch = fetchRoot.fetch(query, { bodies, struct: false });
|
||||
|
||||
fetch.on('error', e => reject(e));
|
||||
|
||||
fetch.on('message', (msg, id) => {
|
||||
messages.set(id, { headers: '', attrs: null as any });
|
||||
|
||||
msg.on('body', stream => stream.on('data',
|
||||
chunk => messages.get(id)!.headers += chunk.toString('utf8')));
|
||||
|
||||
msg.once('attributes',
|
||||
attrs => messages.get(id)!.attrs = attrs);
|
||||
});
|
||||
|
||||
fetch.on('end', () => {
|
||||
resolve(messages);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches messages by SeqNo and returns their raw headers and attributes.
|
||||
*
|
||||
* @param query - The query to send to the server.
|
||||
* @param bodies - The header bodies to fetch.
|
||||
* @returns a map of messages indexed by SeqNo.
|
||||
*/
|
||||
|
||||
async fetchMessagesBySeqNo(query: FetchSpecifier, bodies?: string): Promise<Map<number, FetchMessage>> {
|
||||
return this.fetchMessages(FetchMode.SEQ, query, bodies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches messages by UID and returns their raw headers and attributes.
|
||||
*
|
||||
* @param query - The query to send to the server.
|
||||
* @param bodies - The header bodies to fetch.
|
||||
* @returns a map of messages indexed by UID.
|
||||
*/
|
||||
|
||||
async fetchMessagesByUID(query: FetchSpecifier, bodies?: string): Promise<Map<number, FetchMessage>> {
|
||||
return this.fetchMessages(FetchMode.UID, query, bodies);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import EventEmitter from 'events';
|
||||
|
||||
import Log from '../Log';
|
||||
import ImapConnection, { ConnectionProperties } from './ImapConnection';
|
||||
|
||||
export const DEFAULT_CONNECTIONS = 6;
|
||||
|
||||
export default class ImapController {
|
||||
readonly event: EventEmitter = new EventEmitter();
|
||||
|
||||
private address: string;
|
||||
private connected: boolean = false;
|
||||
private connections: ImapConnection[] = [];
|
||||
|
||||
private awaitingIdle: ((conn: ImapConnection) => void)[] = [];
|
||||
|
||||
constructor(connectionProps: ConnectionProperties, connectionCount: number = DEFAULT_CONNECTIONS) {
|
||||
this.address = connectionProps.user;
|
||||
for (let i = 0; i < connectionCount; i++) this.connections.push(new ImapConnection(connectionProps));
|
||||
|
||||
this.connections.forEach(conn => conn.event.on('idle',
|
||||
() => this.event.emit('idle', conn)));
|
||||
|
||||
this.event.on('idle', (conn: ImapConnection) => {
|
||||
if (this.awaitingIdle.length <= 0) return;
|
||||
this.awaitingIdle[0](conn);
|
||||
this.awaitingIdle.splice(0, 1);
|
||||
});
|
||||
}
|
||||
|
||||
async connect() {
|
||||
await Promise.all(this.connections.map(async (conn, i) => {
|
||||
Log.perfStart(`Connection ${i + 1} for ${this.address}`);
|
||||
await conn.connect();
|
||||
Log.perfEnd(`Connection ${i + 1} for ${this.address}`);
|
||||
}));
|
||||
this.connected = true;
|
||||
}
|
||||
|
||||
async get(path?: string): Promise<ImapConnection> {
|
||||
if (!this.connected) throw new Error('Tried to access a box when the connections aren\'t connected.');
|
||||
|
||||
if (path === undefined) return this.connections[Math.floor(Math.random() * 100000) % this.connections.length];
|
||||
|
||||
for (let conn of this.connections) {
|
||||
if (conn.getOpenBox() === path) {
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
|
||||
for (let conn of this.connections) {
|
||||
if (conn.getOpenBox() === null) {
|
||||
await conn.openBox(path);
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
|
||||
for (let conn of this.connections) {
|
||||
if (!conn.isIdle()) continue;
|
||||
await conn.openBox(path);
|
||||
return conn;
|
||||
}
|
||||
|
||||
return new Promise((resolve) =>
|
||||
this.awaitingIdle.push((conn: ImapConnection) =>
|
||||
conn.openBox(path).then(() => resolve(conn))));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue