@bobfrankston/mailx 1.0.118 → 1.0.119

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
@@ -844,7 +844,37 @@ fetch("/api/version").then(r => r.json()).then(d => {
844
844
  : "";
845
845
  if (el)
846
846
  el.textContent = `mailx v${d.version}${storageLabel}${isApp ? "" : " [browser]"}`;
847
- if (storage.cloudError) {
847
+ if (d.settingsError) {
848
+ showAlert(d.settingsError, "settings-error");
849
+ // Add repair button to the banner
850
+ const banner = document.getElementById("alert-banner");
851
+ if (banner && !banner.querySelector(".repair-btn")) {
852
+ const btn = document.createElement("button");
853
+ btn.className = "repair-btn status-action";
854
+ btn.textContent = "Repair: restore accounts from cache";
855
+ btn.style.cssText = "margin-left:1rem;padding:0.25rem 0.75rem;background:#a6e3a1;color:#1e1e2e;border:none;border-radius:4px;cursor:pointer;font-weight:bold";
856
+ btn.onclick = async () => {
857
+ btn.textContent = "Restoring...";
858
+ btn.disabled = true;
859
+ try {
860
+ const r = await fetch("/api/repair-accounts", { method: "POST" });
861
+ const data = await r.json();
862
+ if (data.ok) {
863
+ hideAlert();
864
+ setTimeout(() => location.reload(), 1000);
865
+ }
866
+ else {
867
+ btn.textContent = `Failed: ${data.error}`;
868
+ }
869
+ }
870
+ catch (e) {
871
+ btn.textContent = `Error: ${e.message}`;
872
+ }
873
+ };
874
+ banner.querySelector("#alert-text")?.after(btn);
875
+ }
876
+ }
877
+ else if (storage.cloudError) {
848
878
  showAlert(`Cloud storage error: ${storage.cloudError}`, "cloud-error");
849
879
  }
850
880
  }).catch(async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.118",
3
+ "version": "1.0.119",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -158,6 +158,48 @@ export function createApiRouter(db, imapManager) {
158
158
  res.status(500).json({ error: e.message });
159
159
  }
160
160
  });
161
+ // ── Repair: restore accounts from DB cache to settings, re-register IMAP ──
162
+ router.post("/repair-accounts", async (req, res) => {
163
+ try {
164
+ // Get accounts from DB (stale but present)
165
+ const dbAccounts = db.getAccountConfigs();
166
+ if (dbAccounts.length === 0) {
167
+ res.json({ ok: false, error: "No cached accounts in database" });
168
+ return;
169
+ }
170
+ // Rebuild account configs from DB's stored config_json
171
+ const restored = [];
172
+ for (const a of dbAccounts) {
173
+ try {
174
+ const cfg = JSON.parse(a.configJson);
175
+ restored.push(cfg);
176
+ }
177
+ catch { /* skip corrupt entries */ }
178
+ }
179
+ if (restored.length === 0) {
180
+ res.json({ ok: false, error: "Could not parse cached account configs" });
181
+ return;
182
+ }
183
+ // Save back to shared dir (and cloud API if active)
184
+ saveAccounts(restored);
185
+ // Re-register in IMAP manager
186
+ for (const acct of restored) {
187
+ try {
188
+ await imapManager.addAccount(acct);
189
+ console.log(` [repair] Re-registered account: ${acct.name} (${acct.id})`);
190
+ }
191
+ catch (e) {
192
+ console.error(` [repair] Failed to register ${acct.id}: ${e.message}`);
193
+ }
194
+ }
195
+ // Start sync
196
+ imapManager.syncAll().catch(() => { });
197
+ res.json({ ok: true, message: `Restored ${restored.length} account(s) and started sync.` });
198
+ }
199
+ catch (e) {
200
+ res.status(500).json({ error: e.message });
201
+ }
202
+ });
161
203
  // ── Send ──
162
204
  router.post("/send", async (req, res) => {
163
205
  try {
@@ -56,6 +56,8 @@ export declare class ImapManager extends EventEmitter {
56
56
  private createClient;
57
57
  /** Track client logout for connection counting */
58
58
  private trackLogout;
59
+ /** Number of registered IMAP accounts */
60
+ getAccountCount(): number;
59
61
  /** Register an account */
60
62
  addAccount(account: AccountConfig): Promise<void>;
61
63
  /** Sync folder list for an account */
@@ -214,6 +214,8 @@ export class ImapManager extends EventEmitter {
214
214
  this.activeConnections.set(accountId, count);
215
215
  console.log(` [conn] ${accountId}: -1 (${count} active)`);
216
216
  }
217
+ /** Number of registered IMAP accounts */
218
+ getAccountCount() { return this.configs.size; }
217
219
  /** Register an account */
218
220
  async addAccount(account) {
219
221
  if (this.configs.has(account.id))
@@ -94,7 +94,13 @@ const apiRouter = createApiRouter(db, imapManager);
94
94
  app.use("/api", apiRouter);
95
95
  app.get("/api/version", (req, res) => {
96
96
  const storage = getStorageInfo();
97
- res.json({ version: SERVER_VERSION, theme: settings.ui?.theme || "system", storage });
97
+ const imapAccounts = imapManager.getAccountCount();
98
+ const dbAccounts = db.getAccounts().length;
99
+ // Warn if DB has accounts but IMAP has none — stale DB, settings missing
100
+ const settingsError = (dbAccounts > 0 && imapAccounts === 0)
101
+ ? "No accounts loaded from settings — showing stale data. Check accounts.jsonc on your cloud drive."
102
+ : undefined;
103
+ res.json({ version: SERVER_VERSION, theme: settings.ui?.theme || "system", storage, imapAccounts, settingsError });
98
104
  });
99
105
  app.all("/info", (req, res) => {
100
106
  res.json({ version: SERVER_VERSION, uptime: Math.round(process.uptime()), port: PORT, imap: imapManager.useNativeClient ? "native" : "imapflow" });
@@ -15,6 +15,12 @@ export declare class MailxDB {
15
15
  email: string;
16
16
  lastSync: number;
17
17
  }[];
18
+ getAccountConfigs(): {
19
+ id: string;
20
+ name: string;
21
+ email: string;
22
+ configJson: string;
23
+ }[];
18
24
  updateLastSync(accountId: string, timestamp: number): void;
19
25
  upsertFolder(accountId: string, folderPath: string, name: string, specialUse: string, delimiter: string): number;
20
26
  getFolders(accountId: string): Folder[];
@@ -137,6 +137,9 @@ export class MailxDB {
137
137
  getAccounts() {
138
138
  return this.db.prepare("SELECT id, name, email, last_sync as lastSync FROM accounts").all();
139
139
  }
140
+ getAccountConfigs() {
141
+ return this.db.prepare("SELECT id, name, email, config_json as configJson FROM accounts").all();
142
+ }
140
143
  updateLastSync(accountId, timestamp) {
141
144
  this.db.prepare("UPDATE accounts SET last_sync = ? WHERE id = ?").run(timestamp, accountId);
142
145
  }