@bobfrankston/rmfmail 1.2.170 → 1.2.172
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/android-bootstrap.bundle.js +117 -40
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/package.json +3 -3
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-settings/index.d.ts.map +1 -1
- package/packages/mailx-settings/index.js +35 -1
- package/packages/mailx-settings/index.js.map +1 -1
- package/packages/mailx-settings/index.ts +32 -1
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-store-web/web-service.d.ts.map +1 -1
- package/packages/mailx-store-web/web-service.js +33 -28
- package/packages/mailx-store-web/web-service.js.map +1 -1
- package/packages/mailx-store-web/web-service.ts +23 -23
- package/packages/mailx-store-web/web-settings.d.ts +12 -1
- package/packages/mailx-store-web/web-settings.d.ts.map +1 -1
- package/packages/mailx-store-web/web-settings.js +112 -15
- package/packages/mailx-store-web/web-settings.js.map +1 -1
- package/packages/mailx-store-web/web-settings.ts +110 -19
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-41184 → node_modules.npmglobalize-stash-74140}/.package-lock.json +0 -0
|
@@ -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) => {
|
|
@@ -2630,6 +2631,15 @@ async function idbDelete(key) {
|
|
|
2630
2631
|
tx.onerror = () => reject(tx.error);
|
|
2631
2632
|
});
|
|
2632
2633
|
}
|
|
2634
|
+
async function idbListKeys(prefix) {
|
|
2635
|
+
const db2 = await openSettingsDb();
|
|
2636
|
+
return new Promise((resolve, reject) => {
|
|
2637
|
+
const tx = db2.transaction(STORE_NAME2, "readonly");
|
|
2638
|
+
const req = tx.objectStore(STORE_NAME2).getAllKeys(IDBKeyRange.bound(prefix, prefix + "\uFFFF"));
|
|
2639
|
+
req.onsuccess = () => resolve(req.result.map(String));
|
|
2640
|
+
req.onerror = () => reject(req.error);
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2633
2643
|
function setGDriveTokenProvider(provider) {
|
|
2634
2644
|
tokenProvider = provider;
|
|
2635
2645
|
}
|
|
@@ -2665,17 +2675,28 @@ async function cloudStat(filename) {
|
|
|
2665
2675
|
return null;
|
|
2666
2676
|
}
|
|
2667
2677
|
}
|
|
2678
|
+
async function gDriveFindFile(filename, token) {
|
|
2679
|
+
const q = encodeURIComponent(`name='${filename}' and '${gDriveFolderId}' in parents and trashed=false`);
|
|
2680
|
+
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}` } });
|
|
2681
|
+
if (!res.ok)
|
|
2682
|
+
return null;
|
|
2683
|
+
const files = (await res.json()).files || [];
|
|
2684
|
+
if (files.length === 0)
|
|
2685
|
+
return null;
|
|
2686
|
+
if (files.length > 1) {
|
|
2687
|
+
console.warn(`[settings] ${filename}: ${files.length} same-named files in the shared folder \u2014 using own/newest. Clean the duplicates (owners differ?).`);
|
|
2688
|
+
files.sort((a, b) => (b.ownedByMe === true ? 1 : 0) - (a.ownedByMe === true ? 1 : 0) || String(b.modifiedTime).localeCompare(String(a.modifiedTime)));
|
|
2689
|
+
}
|
|
2690
|
+
const f = files[0];
|
|
2691
|
+
return { id: f.id, modifiedTime: f.modifiedTime || "", ownedByMe: f.ownedByMe === true, capabilitiesCanEdit: f.capabilities?.canEdit !== false };
|
|
2692
|
+
}
|
|
2668
2693
|
async function gDriveRead(filename) {
|
|
2669
2694
|
if (!tokenProvider || !gDriveFolderId)
|
|
2670
2695
|
return null;
|
|
2671
2696
|
try {
|
|
2672
2697
|
const token = await tokenProvider();
|
|
2673
|
-
const
|
|
2674
|
-
const
|
|
2675
|
-
if (!listRes.ok)
|
|
2676
|
-
return null;
|
|
2677
|
-
const listData = await listRes.json();
|
|
2678
|
-
const fileId = listData.files?.[0]?.id;
|
|
2698
|
+
const found = await gDriveFindFile(filename, token);
|
|
2699
|
+
const fileId = found?.id;
|
|
2679
2700
|
if (!fileId)
|
|
2680
2701
|
return null;
|
|
2681
2702
|
const res = await globalThis.fetch(`https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`, { headers: { "Authorization": `Bearer ${token}` } });
|
|
@@ -2692,13 +2713,13 @@ async function gDriveWrite(filename, content) {
|
|
|
2692
2713
|
return false;
|
|
2693
2714
|
try {
|
|
2694
2715
|
const token = await tokenProvider();
|
|
2695
|
-
const
|
|
2696
|
-
const
|
|
2697
|
-
if (!listRes.ok)
|
|
2698
|
-
return false;
|
|
2699
|
-
const listData = await listRes.json();
|
|
2700
|
-
const fileId = listData.files?.[0]?.id;
|
|
2716
|
+
const found = await gDriveFindFile(filename, token);
|
|
2717
|
+
const fileId = found?.id;
|
|
2701
2718
|
if (fileId) {
|
|
2719
|
+
if (!found.capabilitiesCanEdit) {
|
|
2720
|
+
console.error(`[settings] ${filename}: existing shared file is not editable by this account \u2014 NOT creating a duplicate. Change kept locally.`);
|
|
2721
|
+
return false;
|
|
2722
|
+
}
|
|
2702
2723
|
const res = await globalThis.fetch(`https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=media`, {
|
|
2703
2724
|
method: "PATCH",
|
|
2704
2725
|
headers: {
|
|
@@ -2707,6 +2728,8 @@ async function gDriveWrite(filename, content) {
|
|
|
2707
2728
|
},
|
|
2708
2729
|
body: content
|
|
2709
2730
|
});
|
|
2731
|
+
if (!res.ok)
|
|
2732
|
+
console.error(`[settings] ${filename}: cloud update failed (${res.status})`);
|
|
2710
2733
|
return res.ok;
|
|
2711
2734
|
} else {
|
|
2712
2735
|
const metadata = JSON.stringify({
|
|
@@ -2816,6 +2839,20 @@ function parseJsonc(text) {
|
|
|
2816
2839
|
stripped = stripped.replace(/,(\s*[}\]])/g, "$1");
|
|
2817
2840
|
return JSON.parse(stripped);
|
|
2818
2841
|
}
|
|
2842
|
+
async function stashPrevIdb(filename, aboutToBecome) {
|
|
2843
|
+
try {
|
|
2844
|
+
const old = await idbRead(filename);
|
|
2845
|
+
if (!old || old === aboutToBecome)
|
|
2846
|
+
return;
|
|
2847
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2848
|
+
await idbWrite(`prev:${filename}:${stamp}`, old);
|
|
2849
|
+
const keys = await idbListKeys(`prev:${filename}:`);
|
|
2850
|
+
for (const k of keys.sort().slice(0, Math.max(0, keys.length - PREV_KEEP))) {
|
|
2851
|
+
await idbDelete(k);
|
|
2852
|
+
}
|
|
2853
|
+
} catch {
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2819
2856
|
function refreshSharedFile(filename, cached) {
|
|
2820
2857
|
const now = Date.now();
|
|
2821
2858
|
if (now - (__lastSharedRefresh.get(filename) || 0) < SHARED_REFRESH_MS)
|
|
@@ -2908,6 +2945,7 @@ async function loadPreferences() {
|
|
|
2908
2945
|
}
|
|
2909
2946
|
async function savePreferences(prefs) {
|
|
2910
2947
|
const content = JSON.stringify(prefs, null, 2);
|
|
2948
|
+
await stashPrevIdb("preferences.jsonc", content);
|
|
2911
2949
|
await idbWrite("preferences.jsonc", content);
|
|
2912
2950
|
await gDriveWrite("preferences.jsonc", content);
|
|
2913
2951
|
}
|
|
@@ -2948,9 +2986,44 @@ async function loadAllowlist() {
|
|
|
2948
2986
|
}
|
|
2949
2987
|
return { ...DEFAULT_ALLOWLIST };
|
|
2950
2988
|
}
|
|
2989
|
+
function allowlistEntryCount(l) {
|
|
2990
|
+
return ALLOWLIST_KEYS.reduce((n, k) => n + (Array.isArray(l?.[k]) ? l[k].length : 0), 0);
|
|
2991
|
+
}
|
|
2992
|
+
async function updateAllowlist(mutate) {
|
|
2993
|
+
let base = null;
|
|
2994
|
+
try {
|
|
2995
|
+
const fresh = await gDriveRead("allowlist.jsonc");
|
|
2996
|
+
if (fresh) {
|
|
2997
|
+
await idbWrite("allowlist.jsonc", fresh);
|
|
2998
|
+
try {
|
|
2999
|
+
base = parseJsonc(fresh);
|
|
3000
|
+
} catch {
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
} catch {
|
|
3004
|
+
}
|
|
3005
|
+
if (!base)
|
|
3006
|
+
base = await loadAllowlist();
|
|
3007
|
+
const out = mutate(base) || base;
|
|
3008
|
+
await saveAllowlist(out);
|
|
3009
|
+
return out;
|
|
3010
|
+
}
|
|
2951
3011
|
async function saveAllowlist(list) {
|
|
2952
3012
|
const content = JSON.stringify(list, null, 2);
|
|
3013
|
+
await stashPrevIdb("allowlist.jsonc", content);
|
|
2953
3014
|
await idbWrite("allowlist.jsonc", content);
|
|
3015
|
+
try {
|
|
3016
|
+
const cloudRaw = await gDriveRead("allowlist.jsonc");
|
|
3017
|
+
if (cloudRaw) {
|
|
3018
|
+
const cloudCount = allowlistEntryCount(parseJsonc(cloudRaw));
|
|
3019
|
+
const newCount = allowlistEntryCount(list);
|
|
3020
|
+
if (cloudCount > 10 && newCount < cloudCount / 2) {
|
|
3021
|
+
console.error(`[settings] REFUSING allowlist cloud write: ${cloudCount} \u2192 ${newCount} entries (stale/default base \u2014 see 2026-07-23 wipe). Local cache updated only.`);
|
|
3022
|
+
return;
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
} catch {
|
|
3026
|
+
}
|
|
2954
3027
|
await gDriveWrite("allowlist.jsonc", content);
|
|
2955
3028
|
}
|
|
2956
3029
|
async function loadAutocomplete() {
|
|
@@ -3020,7 +3093,7 @@ async function loadDeviceState() {
|
|
|
3020
3093
|
}
|
|
3021
3094
|
return {};
|
|
3022
3095
|
}
|
|
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;
|
|
3096
|
+
var IDB_NAME2, IDB_VERSION, STORE_NAME2, gDriveFolderId, gDriveFolderName, gDriveFolderPath, gDriveFolderOwner, tokenProvider, PROVIDERS, DEFAULT_PREFERENCES, DEFAULT_ALLOWLIST, PREV_KEEP, SHARED_REFRESH_MS, __lastSharedRefresh, ALLOWLIST_KEYS, DEVICE_ID_KEY;
|
|
3024
3097
|
var init_web_settings = __esm({
|
|
3025
3098
|
"packages/mailx-store-web/web-settings.js"() {
|
|
3026
3099
|
"use strict";
|
|
@@ -3095,8 +3168,10 @@ var init_web_settings = __esm({
|
|
|
3095
3168
|
flaggedSenders: [],
|
|
3096
3169
|
flaggedDomains: []
|
|
3097
3170
|
};
|
|
3171
|
+
PREV_KEEP = 5;
|
|
3098
3172
|
SHARED_REFRESH_MS = 5 * 6e4;
|
|
3099
3173
|
__lastSharedRefresh = /* @__PURE__ */ new Map();
|
|
3174
|
+
ALLOWLIST_KEYS = ["senders", "domains", "recipients", "flaggedSenders", "flaggedDomains"];
|
|
3100
3175
|
DEVICE_ID_KEY = "mailx-device-id";
|
|
3101
3176
|
}
|
|
3102
3177
|
});
|
|
@@ -5939,23 +6014,24 @@ var WebMailxService = class _WebMailxService {
|
|
|
5939
6014
|
* flaggedSenders / flaggedDomains in allowlist.jsonc, which syncs
|
|
5940
6015
|
* back to the cloud copy. */
|
|
5941
6016
|
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
6017
|
const v = (value || "").trim().toLowerCase();
|
|
5946
6018
|
if (!v)
|
|
5947
6019
|
return { flagged: false };
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
arr.
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
6020
|
+
let flagged = false;
|
|
6021
|
+
await updateAllowlist((list) => {
|
|
6022
|
+
const key = type === "sender" ? "flaggedSenders" : "flaggedDomains";
|
|
6023
|
+
const arr = Array.isArray(list[key]) ? list[key] : [];
|
|
6024
|
+
const idx = arr.findIndex((x) => (x || "").toLowerCase() === v);
|
|
6025
|
+
if (idx >= 0) {
|
|
6026
|
+
arr.splice(idx, 1);
|
|
6027
|
+
flagged = false;
|
|
6028
|
+
} else {
|
|
6029
|
+
arr.push(v);
|
|
6030
|
+
flagged = true;
|
|
6031
|
+
}
|
|
6032
|
+
list[key] = arr;
|
|
6033
|
+
return list;
|
|
6034
|
+
});
|
|
5959
6035
|
return { flagged };
|
|
5960
6036
|
}
|
|
5961
6037
|
async updateFlags(accountId, uid, flags) {
|
|
@@ -5964,18 +6040,19 @@ var WebMailxService = class _WebMailxService {
|
|
|
5964
6040
|
}
|
|
5965
6041
|
// ── Remote content allow-list ──
|
|
5966
6042
|
async allowRemoteContent(type, value) {
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
6043
|
+
await updateAllowlist((list) => {
|
|
6044
|
+
if (type === "sender" && !list.senders.includes(value))
|
|
6045
|
+
list.senders.push(value);
|
|
6046
|
+
else if (type === "domain" && !list.domains.includes(value))
|
|
6047
|
+
list.domains.push(value);
|
|
6048
|
+
else if (type === "recipient") {
|
|
6049
|
+
if (!list.recipients)
|
|
6050
|
+
list.recipients = [];
|
|
6051
|
+
if (!list.recipients.includes(value))
|
|
6052
|
+
list.recipients.push(value);
|
|
6053
|
+
}
|
|
6054
|
+
return list;
|
|
6055
|
+
});
|
|
5979
6056
|
}
|
|
5980
6057
|
// ── Search ──
|
|
5981
6058
|
async search(q, page = 1, pageSize = 50, scope = "all", accountId, folderId) {
|