New Imap Handling

master
Auri 10 months ago
parent 01760496ed
commit 10522dc389
  1. 49
      DatabaseLayout.txt
  2. 188
      client/res/logo.svg
  3. 6
      client/src/App.tsx
  4. 2
      common/package.json
  5. 5
      package.json
  6. 1
      server/.eslintrc.js
  7. 279
      server/package-lock.json
  8. 12
      server/package.json
  9. 444
      server/src/Account.ts
  10. 122
      server/src/Graph.ts
  11. 44
      server/src/Imap.ts
  12. 10
      server/src/Log.ts
  13. 84
      server/src/Main.ts
  14. 1
      server/src/Message.ts
  15. 137
      server/src/data/Data.ts
  16. 233
      server/src/imap/ImapConnection.ts
  17. 68
      server/src/imap/ImapController.ts

@ -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==",
"requires": {
"bson": "^4.5.1",
"denque": "^1.5.0",
"mongodb-connection-string-url": "^2.0.0",
"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"
}
},
"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';
export default class Account {
private imap: Imap;
private contacts: Contact[] = [];
private conversations: Conversation[] = [];
// interface Contact {
// name: string;
// addresses: Set<string>;
// }
private name: string;
private image: string;
export default class Account {
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();
Log.perfStart('Synchronizing ' + this.address);
const remoteBoxes = await this.getBoxes();
await this.synchronizeBoxes(remoteBoxes);
await this.synchronizeMessages(remoteBoxes);
Log.perfEnd('Synchronizing ' + this.address);
const messages = await this.fetchAllMessages();
this.conversations = this.createConversations(messages).filter(c => c.active);
this.contacts = this.createContacts(messages);
// 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;
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;
}));
}
}
}));
}
getConversations() {
return this.conversations;
private async getBoxes(): Promise<Map<string, Imap.Box>> {
Log.perfStart('Getting boxes for ' + this.address);
const remoteBoxes = await (await this.conn.get()).getBoxes();
const boxReqs: Promise<void>[] = [];
const boxes: Map<string, Imap.Box> = new Map();
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);
});
}
reqBoxesRecursively(remoteBoxes);
await Promise.all(boxReqs);
Log.perfEnd('Getting boxes for ' + this.address);
return boxes;
}
getContacts() {
return this.contacts;
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;
}
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>'
}];
private cleanSubject(subject: string = '') {
return subject.replace(/^((re|fwd?|b?cc)(:| ) *)*/gi, '').trim();
}
async fetchAllMessages(): Promise<RawMessage[]> {
const boxes = (await this.imap.listBoxes()).filter(box =>
!box.treeTypes.has(MailboxType.Spam) && !box.treeTypes.has(MailboxType.Trash));
// async synchronizeData(): Promise<void> {
// const existingBoxes = await DB.MailboxModel.find({ account: this.data._id });
// const remoteBoxes = (await this.imap.listBoxes());
let allMeta: RawMessage[] = [];
// const newBoxes: (Mailbox & { uidValidity: number })[] = [];
// const boxValidityChanged: { _id: ObjectID, uidValidity: number }[] = [];
// const removedBoxes: Set<ObjectID> = new Set(existingBoxes.map(box => box._id));
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]));
}
// 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;
allMeta = allMeta.sort((a, b) => +a.date - +b.date);
return allMeta;
}
// 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 });
// }
// };
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;
}
}
}
}
// let createdIDs: Map<string, ObjectID> = new Map();
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) ])
});
});
// 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);
// }
conversations.forEach(conversation => {
conversation.participants.delete(this.address);
});
// await Promise.all(boxValidityChanged.map(({ _id, uidValidity }) =>
// DB.MailboxModel.updateOne({ _id }, { uidValidity, uidNext: 1 })));
return conversations.sort((a, b) => +a.date - +b.date);
}
// await DB.MailboxModel.deleteMany({ _id: { $in: [ ...removedBoxes ] } });
private createContacts(messages: RawMessage[]): Contact[] {
const contacts: Contact[] = [];
// 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 + ':*'));
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 contacts: { name: string; addresses: Set<string> }[] = [];
contacts.push({
name: participant.name ?? participant.address,
addresses: new Set([ participant.address ])
});
});
}
// 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;
// }
// }
return contacts;
}
// 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;
// }
private cleanSubjectLine(subject: string = '') {
return subject.replace(/^((re|fwd?|b?cc)(:| ) *)*/gi, '').trim();
}
};

@ -1,61 +1,61 @@
import { buildSchema } from 'graphql';
import { Type, SCHEMA } from 'common/graph';
import Message from './Message';
import Account from './Account';
import Conversation from './Conversation';
export interface Context {
accounts: Record<Type.ID, Account>;
}
export const Schema = buildSchema(SCHEMA);
function messageResolver(message: Message) {
return {
id: message.id,
date: message.date,
from: message.from,
to: message.to,
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 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));
}
};
}
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))
};
// import { buildSchema } from 'graphql';
// import { Type, SCHEMA } from 'common/graph';
// import Message from './Message';
// import Account from './Account';
// import Conversation from './Conversation';
// export interface Context {
// accounts: Record<Type.ID, Account>;
// }
// export const Schema = buildSchema(SCHEMA);
// function messageResolver(message: Message) {
// return {
// id: message.id,
// date: message.date,
// from: message.from,
// to: message.to,
// 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 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));
// }
// };
// }
// 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',
<