@bobfrankston/rmfmail 1.2.295 → 1.2.296
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/.commitmsg +26 -15
- package/client/android-bootstrap.bundle.js +69 -11
- package/client/android-bootstrap.bundle.js.map +3 -3
- package/client/app.bundle.js +10 -0
- package/client/app.bundle.js.map +3 -3
- package/npmchanges.md +21 -0
- package/package.json +8 -8
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +41 -7
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +44 -7
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store-web/android-bootstrap.d.ts.map +1 -1
- package/packages/mailx-store-web/android-bootstrap.js +6 -2
- package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
- package/packages/mailx-store-web/android-bootstrap.ts +8 -2
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-types/index.d.ts +1 -0
- package/packages/mailx-types/index.d.ts.map +1 -1
- package/packages/mailx-types/index.js +1 -0
- package/packages/mailx-types/index.js.map +1 -1
- package/packages/mailx-types/index.ts +1 -0
- package/packages/mailx-types/log-changes.d.ts +29 -0
- package/packages/mailx-types/log-changes.d.ts.map +1 -0
- package/packages/mailx-types/log-changes.js +42 -0
- package/packages/mailx-types/log-changes.js.map +1 -0
- package/packages/mailx-types/log-changes.ts +44 -0
- package/packages/mailx-types/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-52364 → node_modules.npmglobalize-stash-40440}/.package-lock.json +0 -0
package/.commitmsg
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
|
-
A
|
|
1
|
+
A star set on the phone now reaches the desktop in seconds, not minutes
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
the write. A cloud read that started before the write finished after it, and
|
|
6
|
-
its caching step put the pre-change content back on disk. Everything read
|
|
7
|
-
through the sync loadAllowlist() then saw the value the user had just removed,
|
|
8
|
-
until the next poll happened to overwrite it with the truth.
|
|
3
|
+
Bob 2026-08-29: "how long should it take for a * on the phone to get to my
|
|
4
|
+
desktop?" Measured, on his own log: a median of 5 minutes and up to 20.
|
|
9
5
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
Why: gal.iecc.com does not advertise NOTIFY, so the desktop watches INBOX with
|
|
7
|
+
plain IDLE. The server DOES push a flag change there as an unsolicited
|
|
8
|
+
`* 1234 FETCH (FLAGS (\Seen \Flagged))` — iflow-direct dropped it (fixed in
|
|
9
|
+
0.1.67), and mailx had nothing to receive it with. The 5-minute quick check
|
|
10
|
+
could not help either: it is a STATUS poll, and MESSAGES / UNSEEN / UIDNEXT do
|
|
11
|
+
not move when a message is starred. So the only path was the periodic full
|
|
12
|
+
sync's QRESYNC pass — which is why the numbers above are the sync cadence
|
|
13
|
+
rather than any property of flags.
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
The IDLE watcher now takes `onFlagChange` and, debounced 1.5 s, runs the
|
|
16
|
+
folder's normal QRESYNC pass — the same one that already reconciles flags and
|
|
17
|
+
took 53 ms in that log. Marking ten messages read on the phone arrives as ten
|
|
18
|
+
pushes and costs one pass. Our own STOREs bounce back as flag pushes too, so
|
|
19
|
+
the handler skips while this account has sync actions draining; the local store
|
|
20
|
+
already has those.
|
|
21
|
+
|
|
22
|
+
INBOX only, because IDLE watches one mailbox per connection. Flags changed in
|
|
23
|
+
other folders still wait for that folder's turn in the sync cycle.
|
|
24
|
+
|
|
25
|
+
Also in this build: the log noise behind it. `[search]` / `[fetch]` per-command
|
|
26
|
+
lines moved behind iflow's existing `verbose` flag, and the repeated
|
|
27
|
+
per-folder reconcile lines now go through `logIfChanged` — first occurrence,
|
|
28
|
+
every change, and an hourly restatement — instead of once per folder per cycle
|
|
29
|
+
forever. On Android each of those lines was an HTTP request to the log server.
|
|
@@ -2640,6 +2640,28 @@ var init_trust = __esm({
|
|
|
2640
2640
|
}
|
|
2641
2641
|
});
|
|
2642
2642
|
|
|
2643
|
+
// packages/mailx-types/log-changes.js
|
|
2644
|
+
function logIfChanged(key, line, emit = console.log) {
|
|
2645
|
+
const now = Date.now();
|
|
2646
|
+
const prev = lastSeen.get(key);
|
|
2647
|
+
if (prev && prev.line === line && now - prev.at < RESTATE_AFTER_MS)
|
|
2648
|
+
return false;
|
|
2649
|
+
lastSeen.set(key, { line, at: now });
|
|
2650
|
+
emit(prev && prev.line === line ? `${line} (unchanged for an hour)` : line);
|
|
2651
|
+
return true;
|
|
2652
|
+
}
|
|
2653
|
+
function clearLogState(key) {
|
|
2654
|
+
lastSeen.delete(key);
|
|
2655
|
+
}
|
|
2656
|
+
var RESTATE_AFTER_MS, lastSeen;
|
|
2657
|
+
var init_log_changes = __esm({
|
|
2658
|
+
"packages/mailx-types/log-changes.js"() {
|
|
2659
|
+
"use strict";
|
|
2660
|
+
RESTATE_AFTER_MS = 60 * 60 * 1e3;
|
|
2661
|
+
lastSeen = /* @__PURE__ */ new Map();
|
|
2662
|
+
}
|
|
2663
|
+
});
|
|
2664
|
+
|
|
2643
2665
|
// packages/mailx-types/contacts-config.js
|
|
2644
2666
|
function parseContactsConfig(raw) {
|
|
2645
2667
|
if (!raw)
|
|
@@ -3013,6 +3035,7 @@ __export(mailx_types_exports, {
|
|
|
3013
3035
|
answeredOf: () => answeredOf,
|
|
3014
3036
|
assessMessageTrust: () => assessMessageTrust,
|
|
3015
3037
|
buildMimeMessage: () => buildMimeMessage,
|
|
3038
|
+
clearLogState: () => clearLogState,
|
|
3016
3039
|
deletedOf: () => deletedOf,
|
|
3017
3040
|
displayNameClaimsOtherAddress: () => displayNameClaimsOtherAddress,
|
|
3018
3041
|
draftOf: () => draftOf,
|
|
@@ -3028,6 +3051,7 @@ __export(mailx_types_exports, {
|
|
|
3028
3051
|
inviteHasGoogleRsvp: () => inviteHasGoogleRsvp,
|
|
3029
3052
|
isAddressToken: () => isAddressToken,
|
|
3030
3053
|
isJunkPreviewText: () => isJunkPreviewText,
|
|
3054
|
+
logIfChanged: () => logIfChanged,
|
|
3031
3055
|
mergeReminderStates: () => mergeReminderStates,
|
|
3032
3056
|
normalizeReminderState: () => normalizeReminderState,
|
|
3033
3057
|
parseInvite: () => parseInvite,
|
|
@@ -3507,6 +3531,7 @@ var init_mailx_types = __esm({
|
|
|
3507
3531
|
"use strict";
|
|
3508
3532
|
init_contact_rules();
|
|
3509
3533
|
init_trust();
|
|
3534
|
+
init_log_changes();
|
|
3510
3535
|
init_contacts_config();
|
|
3511
3536
|
init_groups();
|
|
3512
3537
|
init_reminder_state();
|
|
@@ -8855,6 +8880,10 @@ var NativeImapClient = class {
|
|
|
8855
8880
|
* a server-side deletion in real time instead of waiting for the next
|
|
8856
8881
|
* periodic poll. Distinct from `idleCallback` (new mail only). */
|
|
8857
8882
|
idleExpungeCallback = null;
|
|
8883
|
+
/** Fired for an unsolicited FETCH carrying FLAGS while parked in IDLE —
|
|
8884
|
+
* another client (a phone, Thunderbird, webmail) changed a flag on a
|
|
8885
|
+
* message in the selected mailbox. */
|
|
8886
|
+
idleFlagCallback = null;
|
|
8858
8887
|
idleRefreshTimer = null;
|
|
8859
8888
|
/** RFC 5465 NOTIFY: fires on unsolicited STATUS responses for non-selected
|
|
8860
8889
|
* mailboxes (the server pushes these when the client has issued NOTIFY
|
|
@@ -9302,7 +9331,8 @@ var NativeImapClient = class {
|
|
|
9302
9331
|
if (uids.length === 0)
|
|
9303
9332
|
return [];
|
|
9304
9333
|
uids.reverse();
|
|
9305
|
-
|
|
9334
|
+
if (this.verbose)
|
|
9335
|
+
console.log(` [fetch] ${uids.length} UIDs since ${sinceUid} (newest first)`);
|
|
9306
9336
|
if (uids.length <= this.fetchChunkSize) {
|
|
9307
9337
|
const msgs = await this.fetchMessages(uids.join(","), options);
|
|
9308
9338
|
if (onChunk)
|
|
@@ -9330,9 +9360,11 @@ var NativeImapClient = class {
|
|
|
9330
9360
|
before: before || void 0
|
|
9331
9361
|
});
|
|
9332
9362
|
const t0 = Date.now();
|
|
9333
|
-
|
|
9363
|
+
if (this.verbose)
|
|
9364
|
+
console.log(` [search] SINCE ${since.toISOString().slice(0, 10)}${before ? ` BEFORE ${before.toISOString().slice(0, 10)}` : ""} \u2014 running...`);
|
|
9334
9365
|
const uids = await this.search(criteria);
|
|
9335
|
-
|
|
9366
|
+
if (this.verbose)
|
|
9367
|
+
console.log(` [search] returned ${uids.length} UIDs in ${Date.now() - t0}ms`);
|
|
9336
9368
|
if (uids.length === 0)
|
|
9337
9369
|
return [];
|
|
9338
9370
|
uids.reverse();
|
|
@@ -9345,7 +9377,8 @@ var NativeImapClient = class {
|
|
|
9345
9377
|
allMessages.push(...msgs);
|
|
9346
9378
|
chunkIndex++;
|
|
9347
9379
|
if (chunkIndex === 1 || chunkIndex % 5 === 0) {
|
|
9348
|
-
|
|
9380
|
+
if (this.verbose)
|
|
9381
|
+
console.log(` [fetch] ${allMessages.length}/${uids.length} messages (chunk ${chunkIndex})`);
|
|
9349
9382
|
}
|
|
9350
9383
|
if (onChunk)
|
|
9351
9384
|
onChunk(msgs);
|
|
@@ -9353,7 +9386,8 @@ var NativeImapClient = class {
|
|
|
9353
9386
|
if (chunkSize < this.fetchChunkSizeMax)
|
|
9354
9387
|
chunkSize = Math.min(chunkSize * 4, this.fetchChunkSizeMax);
|
|
9355
9388
|
}
|
|
9356
|
-
|
|
9389
|
+
if (this.verbose)
|
|
9390
|
+
console.log(` [fetch] done \u2014 ${allMessages.length} messages in ${Date.now() - t0}ms`);
|
|
9357
9391
|
return allMessages;
|
|
9358
9392
|
}
|
|
9359
9393
|
/** Fetch the most recent N messages by sequence number — avoids
|
|
@@ -9369,7 +9403,8 @@ var NativeImapClient = class {
|
|
|
9369
9403
|
const start = Math.max(1, exists - n + 1);
|
|
9370
9404
|
const range = `${start}:${exists}`;
|
|
9371
9405
|
const t0 = Date.now();
|
|
9372
|
-
|
|
9406
|
+
if (this.verbose)
|
|
9407
|
+
console.log(` [fetch-latest] ${this.selectedMailbox || "?"}: sequence ${range} (${Math.min(n, exists)} most recent of ${exists})`);
|
|
9373
9408
|
const items = ["UID", "FLAGS", "ENVELOPE", "RFC822.SIZE", "INTERNALDATE", "BODY.PEEK[HEADER]"];
|
|
9374
9409
|
if (options.source)
|
|
9375
9410
|
items.push("BODY.PEEK[]");
|
|
@@ -9395,7 +9430,8 @@ var NativeImapClient = class {
|
|
|
9395
9430
|
onChunk([]);
|
|
9396
9431
|
}
|
|
9397
9432
|
streamed.reverse();
|
|
9398
|
-
|
|
9433
|
+
if (this.verbose)
|
|
9434
|
+
console.log(` [fetch-latest] done \u2014 ${streamed.length} messages in ${Date.now() - t0}ms`);
|
|
9399
9435
|
return streamed;
|
|
9400
9436
|
}
|
|
9401
9437
|
/** Fetch a single message by UID */
|
|
@@ -9505,9 +9541,10 @@ var NativeImapClient = class {
|
|
|
9505
9541
|
}
|
|
9506
9542
|
}
|
|
9507
9543
|
// ── IDLE ──
|
|
9508
|
-
async startIdle(onNewMail, onExpunge) {
|
|
9544
|
+
async startIdle(onNewMail, onExpunge, onFlagChange) {
|
|
9509
9545
|
this.idleCallback = onNewMail;
|
|
9510
9546
|
this.idleExpungeCallback = onExpunge ?? null;
|
|
9547
|
+
this.idleFlagCallback = onFlagChange ?? null;
|
|
9511
9548
|
this.idleStopped = false;
|
|
9512
9549
|
const beginIdleCycle = async () => {
|
|
9513
9550
|
const tag = nextTag();
|
|
@@ -10002,6 +10039,20 @@ var NativeImapClient = class {
|
|
|
10002
10039
|
this.idleExpungeCallback();
|
|
10003
10040
|
continue;
|
|
10004
10041
|
}
|
|
10042
|
+
if (this.idleTag && resp.tag === "*" && resp.type === "FETCH" && /FLAGS\s*\(/i.test(resp.text)) {
|
|
10043
|
+
if (this.idleFlagCallback) {
|
|
10044
|
+
const seq = parseInt(resp.text, 10);
|
|
10045
|
+
const inner = resp.text.match(/FLAGS\s*\(([^)]*)\)/i)?.[1] || "";
|
|
10046
|
+
const flags = inner.split(/\s+/).filter(Boolean);
|
|
10047
|
+
try {
|
|
10048
|
+
this.idleFlagCallback(Number.isFinite(seq) ? seq : 0, flags);
|
|
10049
|
+
} catch (err) {
|
|
10050
|
+
if (this.verbose)
|
|
10051
|
+
console.error(` [imap] onFlagChange threw: ${err.message}`);
|
|
10052
|
+
}
|
|
10053
|
+
}
|
|
10054
|
+
continue;
|
|
10055
|
+
}
|
|
10005
10056
|
if (this.idleTag && resp.tag === "*" && resp.type === "STATUS" && this.onMailboxStatus) {
|
|
10006
10057
|
const parsed = parseStatusResponseFull(resp.text);
|
|
10007
10058
|
if (parsed) {
|
|
@@ -10515,7 +10566,7 @@ var CompatImapClient = class {
|
|
|
10515
10566
|
this.native.onMailboxStatus = opts.onMailboxStatus;
|
|
10516
10567
|
if (opts?.notifySpec)
|
|
10517
10568
|
await this.native.notify(opts.notifySpec);
|
|
10518
|
-
return this.native.startIdle(onNew, opts?.onExpunge);
|
|
10569
|
+
return this.native.startIdle(onNew, opts?.onExpunge, opts?.onFlagChange);
|
|
10519
10570
|
}
|
|
10520
10571
|
/** Copy a message to another server (cross-account) */
|
|
10521
10572
|
async moveMessageToServer(msg, fromMailbox, targetClient, toMailbox) {
|
|
@@ -11075,6 +11126,7 @@ function b64utf8(s) {
|
|
|
11075
11126
|
}
|
|
11076
11127
|
|
|
11077
11128
|
// packages/mailx-store-web/android-bootstrap.ts
|
|
11129
|
+
init_mailx_types();
|
|
11078
11130
|
var db;
|
|
11079
11131
|
var bodyStore;
|
|
11080
11132
|
var service;
|
|
@@ -11487,12 +11539,18 @@ var AndroidSyncManager = class {
|
|
|
11487
11539
|
const serverUids = new Set(serverUidsArr);
|
|
11488
11540
|
const localUids = this.db.getUidsForFolder(accountId, folderId);
|
|
11489
11541
|
if (serverUidsArr.length === 0 && localUids.length > 0) {
|
|
11490
|
-
|
|
11542
|
+
logIfChanged(
|
|
11543
|
+
`${accountId}/${folder.path}:reconcile`,
|
|
11544
|
+
`[sync] ${folder.path}: reconcile skipped \u2014 server returned empty but local has ${localUids.length}`
|
|
11545
|
+
);
|
|
11491
11546
|
} else {
|
|
11492
11547
|
const toDelete = localUids.filter((uid) => !serverUids.has(uid));
|
|
11493
11548
|
const RECONCILE_DELETE_THRESHOLD = 0.5;
|
|
11494
11549
|
if (localUids.length > 0 && toDelete.length / localUids.length > RECONCILE_DELETE_THRESHOLD) {
|
|
11495
|
-
|
|
11550
|
+
logIfChanged(
|
|
11551
|
+
`${accountId}/${folder.path}:reconcile`,
|
|
11552
|
+
`[sync] ${folder.path}: reconcile refused \u2014 would delete ${toDelete.length}/${localUids.length} (${Math.round(toDelete.length / localUids.length * 100)}%)`
|
|
11553
|
+
);
|
|
11496
11554
|
} else {
|
|
11497
11555
|
for (const uid of toDelete) {
|
|
11498
11556
|
this.db.deleteMessage(accountId, folderId, uid);
|