@zaclinley/dsh-mailharbor 0.18.0 → 0.19.0
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/CHANGELOG.md +15 -0
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +79 -6
- package/lib/client.js.map +2 -2
- package/lib/index.js +109 -4
- package/lib/index.js.map +4 -4
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1121,6 +1121,9 @@ function ensureMailSchema(file) {
|
|
|
1121
1121
|
if (!emailColumns.some((column) => column.name === "references")) {
|
|
1122
1122
|
db.exec('ALTER TABLE emails ADD COLUMN "references" TEXT');
|
|
1123
1123
|
}
|
|
1124
|
+
if (!emailColumns.some((column) => column.name === "refs_indexed")) {
|
|
1125
|
+
db.exec("ALTER TABLE emails ADD COLUMN refs_indexed INTEGER DEFAULT 0");
|
|
1126
|
+
}
|
|
1124
1127
|
} finally {
|
|
1125
1128
|
db.close();
|
|
1126
1129
|
}
|
|
@@ -2969,7 +2972,7 @@ ${conversation.trim()}
|
|
|
2969
2972
|
}
|
|
2970
2973
|
|
|
2971
2974
|
// src/host/version.ts
|
|
2972
|
-
var VERSION = "0.
|
|
2975
|
+
var VERSION = "0.19.0";
|
|
2973
2976
|
|
|
2974
2977
|
// src/host/api.ts
|
|
2975
2978
|
init_smtp();
|
|
@@ -3191,6 +3194,15 @@ function buildRouter(runtime) {
|
|
|
3191
3194
|
}
|
|
3192
3195
|
return { ok: true, count };
|
|
3193
3196
|
});
|
|
3197
|
+
router.get("/maintenance/references", ({ url }) => {
|
|
3198
|
+
const { account } = accountFrom(runtime, url);
|
|
3199
|
+
return runtime.referencesStatus(account);
|
|
3200
|
+
});
|
|
3201
|
+
router.post("/maintenance/references", ({ url }) => {
|
|
3202
|
+
const { account } = accountFrom(runtime, url);
|
|
3203
|
+
runtime.startReferencesBackfill(account);
|
|
3204
|
+
return runtime.referencesStatus(account);
|
|
3205
|
+
});
|
|
3194
3206
|
router.get("/transfer/export", () => {
|
|
3195
3207
|
const config = runtime.current;
|
|
3196
3208
|
return {
|
|
@@ -4304,7 +4316,7 @@ ${signature}`;
|
|
|
4304
4316
|
}
|
|
4305
4317
|
|
|
4306
4318
|
// src/host/runtime.ts
|
|
4307
|
-
import { statSync } from "node:fs";
|
|
4319
|
+
import { existsSync as existsSync8, statSync } from "node:fs";
|
|
4308
4320
|
import { join as join7 } from "node:path";
|
|
4309
4321
|
|
|
4310
4322
|
// src/host/secrets.ts
|
|
@@ -4686,11 +4698,63 @@ var MailFlagsStore = class {
|
|
|
4686
4698
|
}
|
|
4687
4699
|
};
|
|
4688
4700
|
|
|
4701
|
+
// src/host/references.ts
|
|
4702
|
+
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
4703
|
+
function extractReferences(raw, messageId) {
|
|
4704
|
+
const empty = { inReplyTo: null, references: null };
|
|
4705
|
+
if (raw === null || raw === void 0) return empty;
|
|
4706
|
+
const text2 = Buffer.from(raw.subarray(0, Math.min(raw.byteLength, 2e5))).toString("utf8");
|
|
4707
|
+
const separator = text2.search(/\r?\n\r?\n/);
|
|
4708
|
+
const head = separator >= 0 ? text2.slice(0, separator) : text2;
|
|
4709
|
+
const unfolded = head.replace(/\r?\n[ \t]+/g, " ");
|
|
4710
|
+
const value = (name2) => {
|
|
4711
|
+
const match = new RegExp(`^${name2}:\\s*(.*)$`, "im").exec(unfolded);
|
|
4712
|
+
return match === null ? "" : String(match[1] ?? "");
|
|
4713
|
+
};
|
|
4714
|
+
const ids = (value2) => value2.split(/\s+/).map((item) => item.replace(/^<|>$/g, "").toLowerCase()).filter((item) => item.includes("@"));
|
|
4715
|
+
const referenceIds = ids(value("references"));
|
|
4716
|
+
const replyTo = ids(value("in-reply-to"));
|
|
4717
|
+
const filteredRefs = referenceIds.filter((id) => id !== messageId.toLowerCase());
|
|
4718
|
+
return {
|
|
4719
|
+
inReplyTo: replyTo[0] ?? null,
|
|
4720
|
+
references: filteredRefs.length > 0 ? filteredRefs.join(" ") : null
|
|
4721
|
+
};
|
|
4722
|
+
}
|
|
4723
|
+
function backfillReferences(file, batchSize = 300) {
|
|
4724
|
+
ensureMailSchema(file);
|
|
4725
|
+
const db = new DatabaseSync4(file);
|
|
4726
|
+
try {
|
|
4727
|
+
db.exec("PRAGMA busy_timeout = 10000");
|
|
4728
|
+
const rows = db.prepare("SELECT id, message_id, raw_eml FROM emails WHERE refs_indexed = 0 ORDER BY id LIMIT ?").all(batchSize);
|
|
4729
|
+
if (rows.length === 0) return { updated: 0, remaining: 0 };
|
|
4730
|
+
const update = db.prepare(
|
|
4731
|
+
'UPDATE emails SET in_reply_to = ?, "references" = ?, refs_indexed = 1 WHERE id = ?'
|
|
4732
|
+
);
|
|
4733
|
+
db.exec("BEGIN");
|
|
4734
|
+
try {
|
|
4735
|
+
for (const row of rows) {
|
|
4736
|
+
const headers = extractReferences(row.raw_eml, String(row.message_id ?? ""));
|
|
4737
|
+
update.run(headers.inReplyTo, headers.references, Number(row.id));
|
|
4738
|
+
}
|
|
4739
|
+
db.exec("COMMIT");
|
|
4740
|
+
} catch (error) {
|
|
4741
|
+
db.exec("ROLLBACK");
|
|
4742
|
+
throw error;
|
|
4743
|
+
}
|
|
4744
|
+
const remaining = Number(
|
|
4745
|
+
db.prepare("SELECT COUNT(*) AS count FROM emails WHERE refs_indexed = 0").get()?.count ?? 0
|
|
4746
|
+
);
|
|
4747
|
+
return { updated: rows.length, remaining };
|
|
4748
|
+
} finally {
|
|
4749
|
+
db.close();
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4689
4753
|
// src/host/tasks.ts
|
|
4690
4754
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
4691
4755
|
import { mkdirSync as mkdirSync7 } from "node:fs";
|
|
4692
4756
|
import { dirname as dirname4 } from "node:path";
|
|
4693
|
-
import { DatabaseSync as
|
|
4757
|
+
import { DatabaseSync as DatabaseSync5 } from "node:sqlite";
|
|
4694
4758
|
var STATUSES = /* @__PURE__ */ new Set(["NEEDS-ACTION", "IN-PROCESS", "COMPLETED", "CANCELLED"]);
|
|
4695
4759
|
function now() {
|
|
4696
4760
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4739,7 +4803,7 @@ var TaskStore = class {
|
|
|
4739
4803
|
open() {
|
|
4740
4804
|
if (this.db !== null) return this.db;
|
|
4741
4805
|
mkdirSync7(dirname4(this.file), { recursive: true });
|
|
4742
|
-
const db = new
|
|
4806
|
+
const db = new DatabaseSync5(this.file);
|
|
4743
4807
|
db.exec("PRAGMA journal_mode=WAL");
|
|
4744
4808
|
db.exec("PRAGMA busy_timeout=10000");
|
|
4745
4809
|
db.exec(`
|
|
@@ -5165,6 +5229,7 @@ var MailHarborRuntime = class {
|
|
|
5165
5229
|
projectStore = null;
|
|
5166
5230
|
taskStore = null;
|
|
5167
5231
|
flagStores = /* @__PURE__ */ new Map();
|
|
5232
|
+
referenceJobs = /* @__PURE__ */ new Map();
|
|
5168
5233
|
disposed = false;
|
|
5169
5234
|
constructor(options = {}) {
|
|
5170
5235
|
this.config = loadConfig();
|
|
@@ -5264,6 +5329,43 @@ var MailHarborRuntime = class {
|
|
|
5264
5329
|
paths(account) {
|
|
5265
5330
|
return accountRuntime(account);
|
|
5266
5331
|
}
|
|
5332
|
+
referencesStatus(account) {
|
|
5333
|
+
return this.referenceJobs.get(account.id) ?? {
|
|
5334
|
+
running: false,
|
|
5335
|
+
indexed: 0,
|
|
5336
|
+
remaining: null,
|
|
5337
|
+
error: null
|
|
5338
|
+
};
|
|
5339
|
+
}
|
|
5340
|
+
/** Backfill In-Reply-To/References for archived mail, one batch at a time. */
|
|
5341
|
+
startReferencesBackfill(account) {
|
|
5342
|
+
const existing = this.referenceJobs.get(account.id);
|
|
5343
|
+
if (existing?.running === true) return;
|
|
5344
|
+
const dbFile = this.paths(account).mailDb;
|
|
5345
|
+
if (!existsSync8(dbFile)) return;
|
|
5346
|
+
const status = {
|
|
5347
|
+
running: true,
|
|
5348
|
+
indexed: 0,
|
|
5349
|
+
remaining: null,
|
|
5350
|
+
error: null
|
|
5351
|
+
};
|
|
5352
|
+
this.referenceJobs.set(account.id, status);
|
|
5353
|
+
void (async () => {
|
|
5354
|
+
try {
|
|
5355
|
+
for (; ; ) {
|
|
5356
|
+
const result = backfillReferences(dbFile, 300);
|
|
5357
|
+
status.indexed += result.updated;
|
|
5358
|
+
status.remaining = result.remaining;
|
|
5359
|
+
if (result.remaining <= 0 || result.updated === 0) break;
|
|
5360
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
5361
|
+
}
|
|
5362
|
+
} catch (error) {
|
|
5363
|
+
status.error = error instanceof Error ? error.message : String(error);
|
|
5364
|
+
} finally {
|
|
5365
|
+
status.running = false;
|
|
5366
|
+
}
|
|
5367
|
+
})();
|
|
5368
|
+
}
|
|
5267
5369
|
/** Credentials for an account, with a clear message when the user must log in. */
|
|
5268
5370
|
credentialsFor(account) {
|
|
5269
5371
|
const resolved = this.credentials.resolve(account);
|
|
@@ -5833,6 +5935,9 @@ function apply(ctx) {
|
|
|
5833
5935
|
}
|
|
5834
5936
|
const handler = createApiHandler(runtime);
|
|
5835
5937
|
const disposers = [];
|
|
5938
|
+
if (activeAccount2 !== void 0) {
|
|
5939
|
+
setTimeout(() => runtime.startReferencesBackfill(activeAccount2), 3e3);
|
|
5940
|
+
}
|
|
5836
5941
|
const start = () => {
|
|
5837
5942
|
disposers.push(ctx.webServer.register({ kind: "prefix", path: ROUTE, handler }));
|
|
5838
5943
|
if (ctx.tools !== void 0) disposers.push(...registerMailTools(ctx.tools, runtime));
|