@bobfrankston/rmfmail 1.2.170 → 1.2.171

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.
@@ -2588,7 +2588,8 @@ __export(web_settings_exports, {
2588
2588
  savePreferences: () => savePreferences,
2589
2589
  saveSettings: () => saveSettings,
2590
2590
  setGDriveFolderId: () => setGDriveFolderId,
2591
- setGDriveTokenProvider: () => setGDriveTokenProvider
2591
+ setGDriveTokenProvider: () => setGDriveTokenProvider,
2592
+ updateAllowlist: () => updateAllowlist
2592
2593
  });
2593
2594
  function openSettingsDb() {
2594
2595
  return new Promise((resolve, reject) => {
@@ -2665,17 +2666,28 @@ async function cloudStat(filename) {
2665
2666
  return null;
2666
2667
  }
2667
2668
  }
2669
+ async function gDriveFindFile(filename, token) {
2670
+ const q = encodeURIComponent(`name='${filename}' and '${gDriveFolderId}' in parents and trashed=false`);
2671
+ const res = await globalThis.fetch(`https://www.googleapis.com/drive/v3/files?q=${q}&fields=files(id,modifiedTime,ownedByMe,capabilities(canEdit))`, { headers: { "Authorization": `Bearer ${token}` } });
2672
+ if (!res.ok)
2673
+ return null;
2674
+ const files = (await res.json()).files || [];
2675
+ if (files.length === 0)
2676
+ return null;
2677
+ if (files.length > 1) {
2678
+ console.warn(`[settings] ${filename}: ${files.length} same-named files in the shared folder \u2014 using own/newest. Clean the duplicates (owners differ?).`);
2679
+ files.sort((a, b) => (b.ownedByMe === true ? 1 : 0) - (a.ownedByMe === true ? 1 : 0) || String(b.modifiedTime).localeCompare(String(a.modifiedTime)));
2680
+ }
2681
+ const f = files[0];
2682
+ return { id: f.id, modifiedTime: f.modifiedTime || "", ownedByMe: f.ownedByMe === true, capabilitiesCanEdit: f.capabilities?.canEdit !== false };
2683
+ }
2668
2684
  async function gDriveRead(filename) {
2669
2685
  if (!tokenProvider || !gDriveFolderId)
2670
2686
  return null;
2671
2687
  try {
2672
2688
  const token = await tokenProvider();
2673
- const q = encodeURIComponent(`name='${filename}' and '${gDriveFolderId}' in parents and trashed=false`);
2674
- const listRes = await globalThis.fetch(`https://www.googleapis.com/drive/v3/files?q=${q}&fields=files(id)`, { headers: { "Authorization": `Bearer ${token}` } });
2675
- if (!listRes.ok)
2676
- return null;
2677
- const listData = await listRes.json();
2678
- const fileId = listData.files?.[0]?.id;
2689
+ const found = await gDriveFindFile(filename, token);
2690
+ const fileId = found?.id;
2679
2691
  if (!fileId)
2680
2692
  return null;
2681
2693
  const res = await globalThis.fetch(`https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`, { headers: { "Authorization": `Bearer ${token}` } });
@@ -2692,13 +2704,13 @@ async function gDriveWrite(filename, content) {
2692
2704
  return false;
2693
2705
  try {
2694
2706
  const token = await tokenProvider();
2695
- const q = encodeURIComponent(`name='${filename}' and '${gDriveFolderId}' in parents and trashed=false`);
2696
- const listRes = await globalThis.fetch(`https://www.googleapis.com/drive/v3/files?q=${q}&fields=files(id)`, { headers: { "Authorization": `Bearer ${token}` } });
2697
- if (!listRes.ok)
2698
- return false;
2699
- const listData = await listRes.json();
2700
- const fileId = listData.files?.[0]?.id;
2707
+ const found = await gDriveFindFile(filename, token);
2708
+ const fileId = found?.id;
2701
2709
  if (fileId) {
2710
+ if (!found.capabilitiesCanEdit) {
2711
+ console.error(`[settings] ${filename}: existing shared file is not editable by this account \u2014 NOT creating a duplicate. Change kept locally.`);
2712
+ return false;
2713
+ }
2702
2714
  const res = await globalThis.fetch(`https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=media`, {
2703
2715
  method: "PATCH",
2704
2716
  headers: {
@@ -2707,6 +2719,8 @@ async function gDriveWrite(filename, content) {
2707
2719
  },
2708
2720
  body: content
2709
2721
  });
2722
+ if (!res.ok)
2723
+ console.error(`[settings] ${filename}: cloud update failed (${res.status})`);
2710
2724
  return res.ok;
2711
2725
  } else {
2712
2726
  const metadata = JSON.stringify({
@@ -2948,9 +2962,43 @@ async function loadAllowlist() {
2948
2962
  }
2949
2963
  return { ...DEFAULT_ALLOWLIST };
2950
2964
  }
2965
+ function allowlistEntryCount(l) {
2966
+ return ALLOWLIST_KEYS.reduce((n, k) => n + (Array.isArray(l?.[k]) ? l[k].length : 0), 0);
2967
+ }
2968
+ async function updateAllowlist(mutate) {
2969
+ let base = null;
2970
+ try {
2971
+ const fresh = await gDriveRead("allowlist.jsonc");
2972
+ if (fresh) {
2973
+ await idbWrite("allowlist.jsonc", fresh);
2974
+ try {
2975
+ base = parseJsonc(fresh);
2976
+ } catch {
2977
+ }
2978
+ }
2979
+ } catch {
2980
+ }
2981
+ if (!base)
2982
+ base = await loadAllowlist();
2983
+ const out = mutate(base) || base;
2984
+ await saveAllowlist(out);
2985
+ return out;
2986
+ }
2951
2987
  async function saveAllowlist(list) {
2952
2988
  const content = JSON.stringify(list, null, 2);
2953
2989
  await idbWrite("allowlist.jsonc", content);
2990
+ try {
2991
+ const cloudRaw = await gDriveRead("allowlist.jsonc");
2992
+ if (cloudRaw) {
2993
+ const cloudCount = allowlistEntryCount(parseJsonc(cloudRaw));
2994
+ const newCount = allowlistEntryCount(list);
2995
+ if (cloudCount > 10 && newCount < cloudCount / 2) {
2996
+ console.error(`[settings] REFUSING allowlist cloud write: ${cloudCount} \u2192 ${newCount} entries (stale/default base \u2014 see 2026-07-23 wipe). Local cache updated only.`);
2997
+ return;
2998
+ }
2999
+ }
3000
+ } catch {
3001
+ }
2954
3002
  await gDriveWrite("allowlist.jsonc", content);
2955
3003
  }
2956
3004
  async function loadAutocomplete() {
@@ -3020,7 +3068,7 @@ async function loadDeviceState() {
3020
3068
  }
3021
3069
  return {};
3022
3070
  }
3023
- var IDB_NAME2, IDB_VERSION, STORE_NAME2, gDriveFolderId, gDriveFolderName, gDriveFolderPath, gDriveFolderOwner, tokenProvider, PROVIDERS, DEFAULT_PREFERENCES, DEFAULT_ALLOWLIST, SHARED_REFRESH_MS, __lastSharedRefresh, DEVICE_ID_KEY;
3071
+ var IDB_NAME2, IDB_VERSION, STORE_NAME2, gDriveFolderId, gDriveFolderName, gDriveFolderPath, gDriveFolderOwner, tokenProvider, PROVIDERS, DEFAULT_PREFERENCES, DEFAULT_ALLOWLIST, SHARED_REFRESH_MS, __lastSharedRefresh, ALLOWLIST_KEYS, DEVICE_ID_KEY;
3024
3072
  var init_web_settings = __esm({
3025
3073
  "packages/mailx-store-web/web-settings.js"() {
3026
3074
  "use strict";
@@ -3097,6 +3145,7 @@ var init_web_settings = __esm({
3097
3145
  };
3098
3146
  SHARED_REFRESH_MS = 5 * 6e4;
3099
3147
  __lastSharedRefresh = /* @__PURE__ */ new Map();
3148
+ ALLOWLIST_KEYS = ["senders", "domains", "recipients", "flaggedSenders", "flaggedDomains"];
3100
3149
  DEVICE_ID_KEY = "mailx-device-id";
3101
3150
  }
3102
3151
  });
@@ -5939,23 +5988,24 @@ var WebMailxService = class _WebMailxService {
5939
5988
  * flaggedSenders / flaggedDomains in allowlist.jsonc, which syncs
5940
5989
  * back to the cloud copy. */
5941
5990
  async flagSenderOrDomain(type, value) {
5942
- const list = await loadAllowlist();
5943
- const key = type === "sender" ? "flaggedSenders" : "flaggedDomains";
5944
- const arr = Array.isArray(list[key]) ? list[key] : [];
5945
5991
  const v = (value || "").trim().toLowerCase();
5946
5992
  if (!v)
5947
5993
  return { flagged: false };
5948
- const idx = arr.findIndex((x) => (x || "").toLowerCase() === v);
5949
- let flagged;
5950
- if (idx >= 0) {
5951
- arr.splice(idx, 1);
5952
- flagged = false;
5953
- } else {
5954
- arr.push(v);
5955
- flagged = true;
5956
- }
5957
- list[key] = arr;
5958
- await saveAllowlist(list);
5994
+ let flagged = false;
5995
+ await updateAllowlist((list) => {
5996
+ const key = type === "sender" ? "flaggedSenders" : "flaggedDomains";
5997
+ const arr = Array.isArray(list[key]) ? list[key] : [];
5998
+ const idx = arr.findIndex((x) => (x || "").toLowerCase() === v);
5999
+ if (idx >= 0) {
6000
+ arr.splice(idx, 1);
6001
+ flagged = false;
6002
+ } else {
6003
+ arr.push(v);
6004
+ flagged = true;
6005
+ }
6006
+ list[key] = arr;
6007
+ return list;
6008
+ });
5959
6009
  return { flagged };
5960
6010
  }
5961
6011
  async updateFlags(accountId, uid, flags) {
@@ -5964,18 +6014,19 @@ var WebMailxService = class _WebMailxService {
5964
6014
  }
5965
6015
  // ── Remote content allow-list ──
5966
6016
  async allowRemoteContent(type, value) {
5967
- const list = await loadAllowlist();
5968
- if (type === "sender" && !list.senders.includes(value))
5969
- list.senders.push(value);
5970
- else if (type === "domain" && !list.domains.includes(value))
5971
- list.domains.push(value);
5972
- else if (type === "recipient") {
5973
- if (!list.recipients)
5974
- list.recipients = [];
5975
- if (!list.recipients.includes(value))
5976
- list.recipients.push(value);
5977
- }
5978
- await saveAllowlist(list);
6017
+ await updateAllowlist((list) => {
6018
+ if (type === "sender" && !list.senders.includes(value))
6019
+ list.senders.push(value);
6020
+ else if (type === "domain" && !list.domains.includes(value))
6021
+ list.domains.push(value);
6022
+ else if (type === "recipient") {
6023
+ if (!list.recipients)
6024
+ list.recipients = [];
6025
+ if (!list.recipients.includes(value))
6026
+ list.recipients.push(value);
6027
+ }
6028
+ return list;
6029
+ });
5979
6030
  }
5980
6031
  // ── Search ──
5981
6032
  async search(q, page = 1, pageSize = 50, scope = "all", accountId, folderId) {