@remit/mailbox-service 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
|
-
import { MailboxSpecialUse } from "@remit/domain-enums";
|
|
3
|
+
import { MailboxAttribute, MailboxSpecialUse } from "@remit/domain-enums";
|
|
4
4
|
import {
|
|
5
5
|
hasChildren,
|
|
6
6
|
isNoSelect,
|
|
@@ -50,7 +50,33 @@ describe("parseImapAttributes special-use", () => {
|
|
|
50
50
|
it("separates standard attributes from special-use", () => {
|
|
51
51
|
const parsed = parseImapAttributes(["\\HasChildren", "\\Sent"]);
|
|
52
52
|
assert.deepStrictEqual(parsed.specialUse, [MailboxSpecialUse.Sent]);
|
|
53
|
-
assert.deepStrictEqual(parsed.attributes, ["HasChildren"]);
|
|
53
|
+
assert.deepStrictEqual(parsed.attributes, ["\\HasChildren"]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("keeps standard attributes in the RFC 9051 backslash-prefixed form", () => {
|
|
57
|
+
const parsed = parseImapAttributes([
|
|
58
|
+
"\\NonExistent",
|
|
59
|
+
"\\Noinferiors",
|
|
60
|
+
"\\Noselect",
|
|
61
|
+
"\\HasChildren",
|
|
62
|
+
"\\HasNoChildren",
|
|
63
|
+
"\\Marked",
|
|
64
|
+
"\\Unmarked",
|
|
65
|
+
"\\Subscribed",
|
|
66
|
+
"\\Remote",
|
|
67
|
+
]);
|
|
68
|
+
assert.deepStrictEqual(parsed.attributes, [
|
|
69
|
+
"\\NonExistent",
|
|
70
|
+
"\\Noinferiors",
|
|
71
|
+
"\\Noselect",
|
|
72
|
+
"\\HasChildren",
|
|
73
|
+
"\\HasNoChildren",
|
|
74
|
+
"\\Marked",
|
|
75
|
+
"\\Unmarked",
|
|
76
|
+
"\\Subscribed",
|
|
77
|
+
"\\Remote",
|
|
78
|
+
]);
|
|
79
|
+
assert.deepStrictEqual(parsed.attributes, Object.values(MailboxAttribute));
|
|
54
80
|
});
|
|
55
81
|
|
|
56
82
|
it("collects unknown attributes without dropping them", () => {
|
package/src/attribute-mapper.ts
CHANGED
|
@@ -11,8 +11,9 @@ type MailboxSpecialUseValue =
|
|
|
11
11
|
(typeof MailboxSpecialUse)[keyof typeof MailboxSpecialUse];
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Map from IMAP attribute strings to MailboxAttribute enum values
|
|
15
|
-
*
|
|
14
|
+
* Map from IMAP attribute strings to MailboxAttribute enum values.
|
|
15
|
+
* MailboxAttribute keeps the RFC 9051 backslash prefix; MailboxSpecialUse
|
|
16
|
+
* below is a canonical bare designation, so only that map strips it.
|
|
16
17
|
*/
|
|
17
18
|
const ATTRIBUTE_MAP: Record<string, MailboxAttributeValue> = {
|
|
18
19
|
"\\NonExistent": MailboxAttribute.NonExistent,
|
package/src/message-move.ts
CHANGED
|
@@ -695,10 +695,14 @@ export class MessageMoveService {
|
|
|
695
695
|
/**
|
|
696
696
|
* Delete every ThreadMessage row that points at this messageId.
|
|
697
697
|
*
|
|
698
|
-
* A
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
698
|
+
* A row is keyed by (threadId, messageId), so this is one row in practice —
|
|
699
|
+
* a message filed in several folders keeps a single row, because its
|
|
700
|
+
* messageId is derived from the Message-ID header and not from the mailbox.
|
|
701
|
+
* The query stays a list because the key permits more than one thread per
|
|
702
|
+
* message and nothing enforces otherwise.
|
|
703
|
+
*
|
|
704
|
+
* Deleting up-front in the permanent-delete optimistic step prevents stale
|
|
705
|
+
* rows from leaking into mailbox listings while IMAP catches up (#212).
|
|
702
706
|
*/
|
|
703
707
|
private deleteThreadMessagesForMessage = async (
|
|
704
708
|
accountConfigId: string,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
MailboxAttribute,
|
|
5
|
+
MessageKeywordFlag,
|
|
6
|
+
MessageSystemFlag,
|
|
7
|
+
} from "@remit/domain-enums";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* RFC 9051 §2.3.2 system flags and §7.3.1 mailbox attributes are
|
|
11
|
+
* backslash-prefixed on the wire, and these enum values reach an IMAP server
|
|
12
|
+
* verbatim: `handleFlagPush` passes a marker's `flagName` straight to
|
|
13
|
+
* `ImapFlowConnection.addFlags`/`removeFlags`. A value that has lost its
|
|
14
|
+
* backslash is still a legal STORE argument — the server takes it as a custom
|
|
15
|
+
* keyword — so the push silently sets the wrong thing rather than failing.
|
|
16
|
+
*
|
|
17
|
+
* These enums are generated, and the emitter writes their values into
|
|
18
|
+
* JavaScript source where an unescaped `\S` collapses to `S` (issue #64).
|
|
19
|
+
* Pin the exact bytes so a regression fails here instead of on a real
|
|
20
|
+
* mailbox.
|
|
21
|
+
*/
|
|
22
|
+
const SYSTEM_FLAG_WIRE_FORMAT = {
|
|
23
|
+
Seen: "\\Seen",
|
|
24
|
+
Answered: "\\Answered",
|
|
25
|
+
Flagged: "\\Flagged",
|
|
26
|
+
Deleted: "\\Deleted",
|
|
27
|
+
Draft: "\\Draft",
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
const MAILBOX_ATTRIBUTE_WIRE_FORMAT = {
|
|
31
|
+
NonExistent: "\\NonExistent",
|
|
32
|
+
NoInferiors: "\\Noinferiors",
|
|
33
|
+
NoSelect: "\\Noselect",
|
|
34
|
+
HasChildren: "\\HasChildren",
|
|
35
|
+
HasNoChildren: "\\HasNoChildren",
|
|
36
|
+
Marked: "\\Marked",
|
|
37
|
+
Unmarked: "\\Unmarked",
|
|
38
|
+
Subscribed: "\\Subscribed",
|
|
39
|
+
Remote: "\\Remote",
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
describe("IMAP system flag wire format (issue #64)", () => {
|
|
43
|
+
it("carries the RFC 9051 backslash prefix on every member", () => {
|
|
44
|
+
assert.deepEqual({ ...MessageSystemFlag }, { ...SYSTEM_FLAG_WIRE_FORMAT });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
for (const [name, wireValue] of Object.entries(SYSTEM_FLAG_WIRE_FORMAT)) {
|
|
48
|
+
it(`${name} is one backslash followed by the flag name`, () => {
|
|
49
|
+
assert.equal(wireValue.charCodeAt(0), 0x5c);
|
|
50
|
+
assert.equal(wireValue.slice(1), name);
|
|
51
|
+
assert.equal(wireValue.length, name.length + 1);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
it("leaves keyword flags unprefixed", () => {
|
|
56
|
+
for (const wireValue of Object.values(MessageKeywordFlag)) {
|
|
57
|
+
assert.equal(wireValue.startsWith("\\"), false);
|
|
58
|
+
assert.equal(wireValue.startsWith("$"), true);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("IMAP mailbox attribute wire format (issue #64)", () => {
|
|
64
|
+
it("carries the RFC 9051 backslash prefix on every member", () => {
|
|
65
|
+
assert.deepEqual(
|
|
66
|
+
{ ...MailboxAttribute },
|
|
67
|
+
{ ...MAILBOX_ATTRIBUTE_WIRE_FORMAT },
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("prefixes every value with exactly one backslash", () => {
|
|
72
|
+
for (const wireValue of Object.values(MailboxAttribute)) {
|
|
73
|
+
assert.equal(wireValue.charCodeAt(0), 0x5c);
|
|
74
|
+
assert.equal(wireValue.charCodeAt(1) === 0x5c, false);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|