@bobfrankston/mailx 1.0.231 → 1.0.233
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/client/app.js
CHANGED
|
@@ -1154,9 +1154,10 @@ async function openJsoncEditor(initialFile) {
|
|
|
1154
1154
|
<div class="mailx-modal-title">Edit config file</div>
|
|
1155
1155
|
<label class="mailx-modal-label">File
|
|
1156
1156
|
<select class="mailx-modal-input" id="jsonc-file">
|
|
1157
|
-
<option value="accounts.jsonc">accounts.jsonc</option>
|
|
1158
|
-
<option value="allowlist.jsonc">allowlist.jsonc</option>
|
|
1159
|
-
<option value="clients.jsonc">clients.jsonc</option>
|
|
1157
|
+
<option value="accounts.jsonc">accounts.jsonc — accounts (shared via Google Drive)</option>
|
|
1158
|
+
<option value="allowlist.jsonc">allowlist.jsonc — remote-content allowlist (shared)</option>
|
|
1159
|
+
<option value="clients.jsonc">clients.jsonc — per-device registrations (shared)</option>
|
|
1160
|
+
<option value="config.jsonc">config.jsonc — local per-machine overrides (not synced)</option>
|
|
1160
1161
|
</select>
|
|
1161
1162
|
</label>
|
|
1162
1163
|
<label class="mailx-modal-label">Contents (JSONC — comments and trailing commas allowed)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.233",
|
|
4
4
|
"description": "Local-first email client with IMAP sync and standalone native app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/mailx.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@bobfrankston/iflow-node": "^0.1.2",
|
|
25
25
|
"@bobfrankston/miscinfo": "^1.0.8",
|
|
26
26
|
"@bobfrankston/oauthsupport": "^1.0.22",
|
|
27
|
-
"@bobfrankston/msger": "^0.1.
|
|
27
|
+
"@bobfrankston/msger": "^0.1.295",
|
|
28
28
|
"@capacitor/android": "^8.3.0",
|
|
29
29
|
"@capacitor/cli": "^8.3.0",
|
|
30
30
|
"@capacitor/core": "^8.3.0",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"@bobfrankston/iflow-node": "^0.1.2",
|
|
79
79
|
"@bobfrankston/miscinfo": "^1.0.8",
|
|
80
80
|
"@bobfrankston/oauthsupport": "^1.0.22",
|
|
81
|
-
"@bobfrankston/msger": "^0.1.
|
|
81
|
+
"@bobfrankston/msger": "^0.1.295",
|
|
82
82
|
"@capacitor/android": "^8.3.0",
|
|
83
83
|
"@capacitor/cli": "^8.3.0",
|
|
84
84
|
"@capacitor/core": "^8.3.0",
|
|
@@ -62,7 +62,8 @@ export declare class MailxService {
|
|
|
62
62
|
/** Get all messages in a thread (across folders) for an account. */
|
|
63
63
|
getThreadMessages(accountId: string, threadId: string): any;
|
|
64
64
|
/** Read a JSONC config file from the shared cloud dir or local ~/.mailx.
|
|
65
|
-
* Names are whitelisted so the UI can't read arbitrary files.
|
|
65
|
+
* Names are whitelisted so the UI can't read arbitrary files.
|
|
66
|
+
* `config.jsonc` is the local per-machine config (not cloud-synced). */
|
|
66
67
|
readJsoncFile(name: string): Promise<string | null>;
|
|
67
68
|
/** Write a JSONC config file. Validates that the content parses as JSONC
|
|
68
69
|
* (loosely — strips comments/trailing commas) before writing. */
|
|
@@ -664,19 +664,29 @@ export class MailxService {
|
|
|
664
664
|
return this.db.getThreadMessages(accountId, threadId);
|
|
665
665
|
}
|
|
666
666
|
/** Read a JSONC config file from the shared cloud dir or local ~/.mailx.
|
|
667
|
-
* Names are whitelisted so the UI can't read arbitrary files.
|
|
667
|
+
* Names are whitelisted so the UI can't read arbitrary files.
|
|
668
|
+
* `config.jsonc` is the local per-machine config (not cloud-synced). */
|
|
668
669
|
async readJsoncFile(name) {
|
|
669
|
-
const
|
|
670
|
-
if (!
|
|
670
|
+
const WHITELIST = ["accounts.jsonc", "allowlist.jsonc", "clients.jsonc", "config.jsonc"];
|
|
671
|
+
if (!WHITELIST.includes(name))
|
|
671
672
|
throw new Error(`File not allowed: ${name}`);
|
|
673
|
+
if (name === "config.jsonc") {
|
|
674
|
+
const configPath = path.join(getConfigDir(), "config.jsonc");
|
|
675
|
+
try {
|
|
676
|
+
return fs.readFileSync(configPath, "utf-8");
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
672
682
|
const { cloudRead } = await import("@bobfrankston/mailx-settings");
|
|
673
683
|
return cloudRead(name);
|
|
674
684
|
}
|
|
675
685
|
/** Write a JSONC config file. Validates that the content parses as JSONC
|
|
676
686
|
* (loosely — strips comments/trailing commas) before writing. */
|
|
677
687
|
async writeJsoncFile(name, content) {
|
|
678
|
-
const
|
|
679
|
-
if (!
|
|
688
|
+
const WHITELIST = ["accounts.jsonc", "allowlist.jsonc", "clients.jsonc", "config.jsonc"];
|
|
689
|
+
if (!WHITELIST.includes(name))
|
|
680
690
|
throw new Error(`File not allowed: ${name}`);
|
|
681
691
|
// Validate the content parses before writing
|
|
682
692
|
const { parse: parseJsonc } = await import("jsonc-parser");
|
|
@@ -685,6 +695,11 @@ export class MailxService {
|
|
|
685
695
|
if (errors.length) {
|
|
686
696
|
throw new Error(`JSONC parse error: ${errors.map(e => e.error).join(", ")}`);
|
|
687
697
|
}
|
|
698
|
+
if (name === "config.jsonc") {
|
|
699
|
+
const configPath = path.join(getConfigDir(), "config.jsonc");
|
|
700
|
+
fs.writeFileSync(configPath, content);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
688
703
|
const { cloudWrite } = await import("@bobfrankston/mailx-settings");
|
|
689
704
|
const ok = await cloudWrite(name, content);
|
|
690
705
|
if (!ok)
|
|
@@ -127,8 +127,10 @@ export class MailxDB {
|
|
|
127
127
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
128
128
|
this.db.exec(SCHEMA);
|
|
129
129
|
// Idempotent migrations for older databases that predate new columns.
|
|
130
|
-
// SQLite doesn't support "ADD COLUMN IF NOT EXISTS", so we
|
|
131
|
-
//
|
|
130
|
+
// SQLite doesn't support "ADD COLUMN IF NOT EXISTS", so we just try the
|
|
131
|
+
// ALTER and catch the "duplicate column" error. Simpler and more robust
|
|
132
|
+
// than probing via PRAGMA table_info (which can behave differently
|
|
133
|
+
// across sqlite drivers).
|
|
132
134
|
this.addColumnIfMissing("messages", "thread_id", "TEXT");
|
|
133
135
|
try {
|
|
134
136
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_messages_thread_id ON messages(account_id, thread_id)");
|
|
@@ -137,10 +139,17 @@ export class MailxDB {
|
|
|
137
139
|
}
|
|
138
140
|
/** Idempotently add a column to a table if it's missing. */
|
|
139
141
|
addColumnIfMissing(table, column, sqlType) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
try {
|
|
143
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${sqlType}`);
|
|
144
|
+
console.log(` [db] added column ${table}.${column}`);
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
const msg = String(e?.message || e);
|
|
148
|
+
// "duplicate column name" is the expected case when column already exists
|
|
149
|
+
if (!/duplicate column/i.test(msg)) {
|
|
150
|
+
console.error(` [db] migration ${table}.${column} failed: ${msg}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
144
153
|
}
|
|
145
154
|
/** Compute a thread id for an incoming message. Strategy:
|
|
146
155
|
* 1. If any ancestor (in_reply_to or references) is already present in
|