@bobfrankston/mailx-settings 0.1.71 → 0.1.73
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/index.d.ts +10 -3
- package/index.d.ts.map +1 -1
- package/index.js +49 -9
- package/package.json +3 -3
package/index.d.ts
CHANGED
|
@@ -24,7 +24,6 @@ type CloudErrorListener = (error: string | null, context?: {
|
|
|
24
24
|
}) => void;
|
|
25
25
|
export declare function onCloudError(cb: CloudErrorListener): () => void;
|
|
26
26
|
declare function getSharedDir(): string;
|
|
27
|
-
/** Read a file via cloud API (when filesystem mount not available) */
|
|
28
27
|
export declare function cloudRead(filename: string): Promise<string | null>;
|
|
29
28
|
/** Read a file from the shared cloud config folder as raw bytes, base64-encoded.
|
|
30
29
|
* Binary-safe sibling of cloudRead — used for the optional custom reminder
|
|
@@ -222,9 +221,17 @@ export declare function loadAllowlistAsync(): Promise<typeof DEFAULT_ALLOWLIST>;
|
|
|
222
221
|
* one making a change. Never drops a key either side has — that property is
|
|
223
222
|
* what keeps a second machine's entries alive, and it is why this file's
|
|
224
223
|
* lists survived the 2026-07-23 wipe.
|
|
224
|
+
*
|
|
225
|
+
* A union cannot express a REMOVAL, though: the entry the caller just deleted
|
|
226
|
+
* is still in the cloud copy and comes straight back. Untrusting a sender —
|
|
227
|
+
* and unflagging one, which has had this bug since flagSenderOrDomain was
|
|
228
|
+
* written — silently did nothing. So a removal has to be stated rather than
|
|
229
|
+
* inferred: `removals` names, per key, the values this write is deleting on
|
|
230
|
+
* purpose, and they are subtracted after the union. Everything not named is
|
|
231
|
+
* still additive, so a second machine's entries are as safe as before.
|
|
225
232
|
*/
|
|
226
|
-
export declare function mergeAllowlists(local: any, cloud: any): any;
|
|
227
|
-
export declare function saveAllowlist(list: typeof DEFAULT_ALLOWLIST): Promise<void>;
|
|
233
|
+
export declare function mergeAllowlists(local: any, cloud: any, removals?: Record<string, string[]>): any;
|
|
234
|
+
export declare function saveAllowlist(list: typeof DEFAULT_ALLOWLIST, removals?: Record<string, string[]>): Promise<void>;
|
|
228
235
|
/** Load user-added dictionary words. Mirrored to GDrive so "Add to dictionary"
|
|
229
236
|
* on one machine appears on every machine. Returns a deduped string array.
|
|
230
237
|
* Tries the cloud copy first (so a fresh machine sees prior words), then
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AA0G3H,QAAA,MAAM,SAAS,QAA4E,CAAC;AA0E5F,qFAAqF;AACrF,KAAK,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE;IAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAC;AAE/G,wBAAgB,YAAY,CAAC,EAAE,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAM/D;AAeD,iBAAS,YAAY,IAAI,MAAM,CAgB9B;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AA0G3H,QAAA,MAAM,SAAS,QAA4E,CAAC;AA0E5F,qFAAqF;AACrF,KAAK,kBAAkB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE;IAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAC;AAE/G,wBAAgB,YAAY,CAAC,EAAE,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAM/D;AAeD,iBAAS,YAAY,IAAI,MAAM,CAgB9B;AAsBD,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA2DxE;AAED;;;;8BAI8B;AAC9B,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkB9E;AA6BD;;qCAEqC;AACrC,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCjF;AAyBD,4CAA4C;AAC5C,wBAAgB,cAAc,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CA+B3L;AAsFD,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,CAAA;KAAE,CAAC;IAChF,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,CAAA;KAAE,CAAC;CASnF;AAmDD;;;;uEAIuE;AACvE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE9E;AAED;;;;;;;;;;;;4EAY4E;AAC5E,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAcpD;AAED;;;0EAG0E;AAC1E,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;qDAEqD;AACrD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,GAAG,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,aAAa,CA8DzH;AAMD,QAAA,MAAM,mBAAmB;;eAEE,QAAQ,GAAG,MAAM,GAAG,OAAO;gBAC3B,OAAO,GAAG,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;CA+B5C,CAAC;AAEF,QAAA,MAAM,oBAAoB,EAAE,oBAS3B,CAAC;AAEF,QAAA,MAAM,iBAAiB;aACJ,MAAM,EAAE;aACR,MAAM,EAAE;gBACL,MAAM,EAAE;oBAOJ,MAAM,EAAE;oBACR,MAAM,EAAE;oBAIR,MAAM,EAAE;oBACR,MAAM,EAAE;CACjC,CAAC;AAIF,2BAA2B;AAC3B,wBAAgB,YAAY,IAAI,aAAa,EAAE,CA4C9C;AAoCD;;;;0CAI0C;AAC1C,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAuBlE;AAED;;;;;;;;;;;;;;;iDAeiD;AACjD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,GAAG,CA8ChF;AAED,2BAA2B;AAC3B;;;oEAGoE;AACpE,wBAAsB,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAyC3E;AAED;;;wEAGwE;AACxE,wBAAgB,QAAQ,IAAI,MAAM,CAWjC;AAED;;4DAE4D;AAC5D,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB1D;AAED;;;;;uEAKuE;AACvE,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAmB7D;AAED;;;;;;kCAMkC;AAClC,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC,CAa9D;AAED;;;;;0CAK0C;AAC1C,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC,CAYlE;AAED,wEAAwE;AACxE,wBAAgB,eAAe,IAAI,OAAO,mBAAmB,CAkC5D;AAED,uBAAuB;AACvB,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI,CAEhD;AAED,iCAAiC;AACjC,wBAAgB,gBAAgB,IAAI,oBAAoB,CAGvD;AAED,iCAAiC;AACjC,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAIrE;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,IAAI,OAAO,iBAAiB,CAExD;AAED;;;;kFAIkF;AAClF,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,OAAO,iBAAiB,CAAC,CAgB5E;AAED,4EAA4E;AAC5E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,CAahG;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,OAAO,iBAAiB,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAatH;AAgCD;;;oEAGoE;AACpE,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAYtD;AAED;sDACsD;AACtD,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBjE;AAQD,sEAAsE;AACtE,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,aAAa,CAAC,CAahE;AAED;;;mEAGmE;AACnE,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CA+BpF;AAcD,6EAA6E;AAC7E,wBAAgB,YAAY,IAAI,aAAa,CA0B5C;AAyBD,wBAAsB,YAAY,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBzE;AAED,oCAAoC;AACpC,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED,qDAAqD;AACrD,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wCAAwC;AACxC,OAAO,EAAE,YAAY,EAAE,CAAC;AAKxB,kDAAkD;AAClD,wBAAgB,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAkB5E;AAED;;;mFAGmF;AACnF,wBAAsB,eAAe,CAAC,QAAQ,GAAE,QAAQ,GAAG,UAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/F;AAED,QAAA,MAAM,gBAAgB,EAAE,aAMvB,CAAC;AAEF,8FAA8F;AAC9F,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAQzD;AAED,uEAAuE;AACvE,wBAAgB,WAAW,IAAI,OAAO,CAGrC;AAED,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,SAAS,EAAE,CAAC;AAErG;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuDlE"}
|
package/index.js
CHANGED
|
@@ -240,9 +240,23 @@ function getSharedDir() {
|
|
|
240
240
|
* corruption case where 1.0.504 cached a wrong ID and never re-discovered. */
|
|
241
241
|
let folderIdValidated = false;
|
|
242
242
|
/** Read a file via cloud API (when filesystem mount not available) */
|
|
243
|
+
/**
|
|
244
|
+
* When each shared file was last written locally.
|
|
245
|
+
*
|
|
246
|
+
* A cloud read that STARTED before a local write finishes after it, and its
|
|
247
|
+
* caching step then puts the pre-change content back on disk — so the value
|
|
248
|
+
* the user just set reads back stale until the next poll. Found 2026-08-28
|
|
249
|
+
* untrusting a sender: the removal reached Drive correctly (a clean restart
|
|
250
|
+
* pulled the right list) while the local copy still had the entry, mtime
|
|
251
|
+
* stamped at the moment of the write. Same shape as the contacts.jsonc
|
|
252
|
+
* clobber, and the same lesson: the local file is not a cache to be
|
|
253
|
+
* overwritten, it is a copy someone may have just edited.
|
|
254
|
+
*/
|
|
255
|
+
const lastLocalWrite = new Map();
|
|
243
256
|
export async function cloudRead(filename) {
|
|
244
257
|
if (!pendingCloudConfig)
|
|
245
258
|
return null;
|
|
259
|
+
const startedAt = Date.now();
|
|
246
260
|
// Validate cached folderId once per process. If it points somewhere
|
|
247
261
|
// other than an `.rmfmail` folder (e.g. an orphan from a buggy version),
|
|
248
262
|
// drop it and re-discover via gDriveFindOrCreateFolder.
|
|
@@ -287,9 +301,18 @@ export async function cloudRead(filename) {
|
|
|
287
301
|
// Cache locally — stashing the outgoing version in prev/ first so a
|
|
288
302
|
// remote clobber landing here leaves a local trail to recover from.
|
|
289
303
|
try {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
304
|
+
// Skip the cache write when this file changed locally while the
|
|
305
|
+
// read was in flight: that content is older than what is on disk,
|
|
306
|
+
// and the local write has already gone to the cloud.
|
|
307
|
+
const wroteAt = lastLocalWrite.get(filename) || 0;
|
|
308
|
+
if (wroteAt >= startedAt) {
|
|
309
|
+
console.log(` [cloud] ${filename} changed locally during the read — keeping the local copy`);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
fs.mkdirSync(LOCAL_DIR, { recursive: true });
|
|
313
|
+
stashPrev(filename, content);
|
|
314
|
+
fs.writeFileSync(path.join(LOCAL_DIR, filename), content);
|
|
315
|
+
}
|
|
293
316
|
}
|
|
294
317
|
catch { /* ignore cache write failure */ }
|
|
295
318
|
}
|
|
@@ -515,6 +538,9 @@ function loadFile(filename, defaults) {
|
|
|
515
538
|
* a banner. The cloud write is fire-and-forget at this layer; callers
|
|
516
539
|
* who need the result should use the typed save* helpers below. */
|
|
517
540
|
function saveFile(filename, data) {
|
|
541
|
+
// Stamp BEFORE writing: a cloud read racing this write must lose, and the
|
|
542
|
+
// conservative direction is to treat the local copy as newer.
|
|
543
|
+
lastLocalWrite.set(filename, Date.now());
|
|
518
544
|
const sharedDir = getSharedDir();
|
|
519
545
|
atomicWrite(path.join(sharedDir, filename), data);
|
|
520
546
|
// Also update local cache
|
|
@@ -1219,26 +1245,40 @@ export async function loadAllowlistAsync() {
|
|
|
1219
1245
|
* one making a change. Never drops a key either side has — that property is
|
|
1220
1246
|
* what keeps a second machine's entries alive, and it is why this file's
|
|
1221
1247
|
* lists survived the 2026-07-23 wipe.
|
|
1248
|
+
*
|
|
1249
|
+
* A union cannot express a REMOVAL, though: the entry the caller just deleted
|
|
1250
|
+
* is still in the cloud copy and comes straight back. Untrusting a sender —
|
|
1251
|
+
* and unflagging one, which has had this bug since flagSenderOrDomain was
|
|
1252
|
+
* written — silently did nothing. So a removal has to be stated rather than
|
|
1253
|
+
* inferred: `removals` names, per key, the values this write is deleting on
|
|
1254
|
+
* purpose, and they are subtracted after the union. Everything not named is
|
|
1255
|
+
* still additive, so a second machine's entries are as safe as before.
|
|
1222
1256
|
*/
|
|
1223
|
-
export function mergeAllowlists(local, cloud) {
|
|
1257
|
+
export function mergeAllowlists(local, cloud, removals) {
|
|
1224
1258
|
const union = (a, b) => [...new Set([...(a || []), ...(b || [])])];
|
|
1225
1259
|
const out = {};
|
|
1226
1260
|
for (const key of new Set([...Object.keys(local || {}), ...Object.keys(cloud || {})])) {
|
|
1227
1261
|
const mine = (local || {})[key], theirs = (cloud || {})[key];
|
|
1228
|
-
|
|
1229
|
-
|
|
1262
|
+
if (!Array.isArray(mine) && !Array.isArray(theirs)) {
|
|
1263
|
+
out[key] = mine !== undefined ? mine : theirs;
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
const gone = new Set((removals?.[key] || []).map(v => (v || "").toLowerCase()));
|
|
1267
|
+
out[key] = union(mine, theirs).filter(v => !gone.has((v || "").toLowerCase()));
|
|
1230
1268
|
}
|
|
1231
1269
|
return out;
|
|
1232
1270
|
}
|
|
1233
|
-
export async function saveAllowlist(list) {
|
|
1234
|
-
// Read current cloud version and merge (other clients may have added
|
|
1271
|
+
export async function saveAllowlist(list, removals) {
|
|
1272
|
+
// Read current cloud version and merge (other clients may have added
|
|
1273
|
+
// entries). `removals` names what this write deletes on purpose — without
|
|
1274
|
+
// it the union hands the entry straight back.
|
|
1235
1275
|
let merged = { ...list };
|
|
1236
1276
|
try {
|
|
1237
1277
|
const cloudContent = await cloudRead("allowlist.jsonc");
|
|
1238
1278
|
if (cloudContent) {
|
|
1239
1279
|
const cloud = parseJsonc(cloudContent);
|
|
1240
1280
|
if (cloud)
|
|
1241
|
-
merged = mergeAllowlists(list, cloud);
|
|
1281
|
+
merged = mergeAllowlists(list, cloud, removals);
|
|
1242
1282
|
}
|
|
1243
1283
|
}
|
|
1244
1284
|
catch { /* cloud read failed — save local version */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-settings",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.73",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"license": "ISC",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
20
|
+
"@bobfrankston/mailx-types": "^0.1.75",
|
|
21
21
|
"jsonc-parser": "^3.3.1"
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
".transformedSnapshot": {
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
36
|
+
"@bobfrankston/mailx-types": "^0.1.75",
|
|
37
37
|
"jsonc-parser": "^3.3.1"
|
|
38
38
|
}
|
|
39
39
|
}
|