@bobfrankston/rmfmail 1.2.169 → 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({
@@ -2816,6 +2830,22 @@ function parseJsonc(text) {
2816
2830
  stripped = stripped.replace(/,(\s*[}\]])/g, "$1");
2817
2831
  return JSON.parse(stripped);
2818
2832
  }
2833
+ function refreshSharedFile(filename, cached) {
2834
+ const now = Date.now();
2835
+ if (now - (__lastSharedRefresh.get(filename) || 0) < SHARED_REFRESH_MS)
2836
+ return;
2837
+ __lastSharedRefresh.set(filename, now);
2838
+ void (async () => {
2839
+ try {
2840
+ const fresh = await gDriveRead(filename);
2841
+ if (fresh && fresh !== cached) {
2842
+ await idbWrite(filename, fresh);
2843
+ console.log(`[settings] ${filename} refreshed from GDrive (shared copy changed)`);
2844
+ }
2845
+ } catch {
2846
+ }
2847
+ })();
2848
+ }
2819
2849
  async function loadAccounts() {
2820
2850
  const cached = await idbRead("accounts.jsonc");
2821
2851
  if (cached) {
@@ -2864,6 +2894,7 @@ async function saveAccounts(accounts) {
2864
2894
  async function loadPreferences() {
2865
2895
  const cached = await idbRead("preferences.jsonc");
2866
2896
  if (cached) {
2897
+ refreshSharedFile("preferences.jsonc", cached);
2867
2898
  try {
2868
2899
  const data = parseJsonc(cached);
2869
2900
  return {
@@ -2915,6 +2946,7 @@ async function saveSettings(settings) {
2915
2946
  async function loadAllowlist() {
2916
2947
  const cached = await idbRead("allowlist.jsonc");
2917
2948
  if (cached) {
2949
+ refreshSharedFile("allowlist.jsonc", cached);
2918
2950
  try {
2919
2951
  return parseJsonc(cached);
2920
2952
  } catch {
@@ -2930,9 +2962,43 @@ async function loadAllowlist() {
2930
2962
  }
2931
2963
  return { ...DEFAULT_ALLOWLIST };
2932
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
+ }
2933
2987
  async function saveAllowlist(list) {
2934
2988
  const content = JSON.stringify(list, null, 2);
2935
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
+ }
2936
3002
  await gDriveWrite("allowlist.jsonc", content);
2937
3003
  }
2938
3004
  async function loadAutocomplete() {
@@ -3002,7 +3068,7 @@ async function loadDeviceState() {
3002
3068
  }
3003
3069
  return {};
3004
3070
  }
3005
- var IDB_NAME2, IDB_VERSION, STORE_NAME2, gDriveFolderId, gDriveFolderName, gDriveFolderPath, gDriveFolderOwner, tokenProvider, PROVIDERS, DEFAULT_PREFERENCES, DEFAULT_ALLOWLIST, 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;
3006
3072
  var init_web_settings = __esm({
3007
3073
  "packages/mailx-store-web/web-settings.js"() {
3008
3074
  "use strict";
@@ -3077,6 +3143,9 @@ var init_web_settings = __esm({
3077
3143
  flaggedSenders: [],
3078
3144
  flaggedDomains: []
3079
3145
  };
3146
+ SHARED_REFRESH_MS = 5 * 6e4;
3147
+ __lastSharedRefresh = /* @__PURE__ */ new Map();
3148
+ ALLOWLIST_KEYS = ["senders", "domains", "recipients", "flaggedSenders", "flaggedDomains"];
3080
3149
  DEVICE_ID_KEY = "mailx-device-id";
3081
3150
  }
3082
3151
  });
@@ -5919,23 +5988,24 @@ var WebMailxService = class _WebMailxService {
5919
5988
  * flaggedSenders / flaggedDomains in allowlist.jsonc, which syncs
5920
5989
  * back to the cloud copy. */
5921
5990
  async flagSenderOrDomain(type, value) {
5922
- const list = await loadAllowlist();
5923
- const key = type === "sender" ? "flaggedSenders" : "flaggedDomains";
5924
- const arr = Array.isArray(list[key]) ? list[key] : [];
5925
5991
  const v = (value || "").trim().toLowerCase();
5926
5992
  if (!v)
5927
5993
  return { flagged: false };
5928
- const idx = arr.findIndex((x) => (x || "").toLowerCase() === v);
5929
- let flagged;
5930
- if (idx >= 0) {
5931
- arr.splice(idx, 1);
5932
- flagged = false;
5933
- } else {
5934
- arr.push(v);
5935
- flagged = true;
5936
- }
5937
- list[key] = arr;
5938
- 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
+ });
5939
6009
  return { flagged };
5940
6010
  }
5941
6011
  async updateFlags(accountId, uid, flags) {
@@ -5944,18 +6014,19 @@ var WebMailxService = class _WebMailxService {
5944
6014
  }
5945
6015
  // ── Remote content allow-list ──
5946
6016
  async allowRemoteContent(type, value) {
5947
- const list = await loadAllowlist();
5948
- if (type === "sender" && !list.senders.includes(value))
5949
- list.senders.push(value);
5950
- else if (type === "domain" && !list.domains.includes(value))
5951
- list.domains.push(value);
5952
- else if (type === "recipient") {
5953
- if (!list.recipients)
5954
- list.recipients = [];
5955
- if (!list.recipients.includes(value))
5956
- list.recipients.push(value);
5957
- }
5958
- 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
+ });
5959
6030
  }
5960
6031
  // ── Search ──
5961
6032
  async search(q, page = 1, pageSize = 50, scope = "all", accountId, folderId) {