@bobfrankston/rmfmail 1.2.175 → 1.2.178

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.
Files changed (57) hide show
  1. package/TODO.md +2 -2
  2. package/client/android-bootstrap.bundle.js +112 -0
  3. package/client/android-bootstrap.bundle.js.map +3 -3
  4. package/client/app.bundle.js +79 -16
  5. package/client/app.bundle.js.map +3 -3
  6. package/client/components/alarms.js +107 -20
  7. package/client/components/alarms.js.map +1 -1
  8. package/client/components/alarms.ts +104 -20
  9. package/client/compose/compose.bundle.js +4 -0
  10. package/client/compose/compose.bundle.js.map +2 -2
  11. package/client/lib/api-client.js +6 -0
  12. package/client/lib/api-client.js.map +1 -1
  13. package/client/lib/api-client.ts +9 -0
  14. package/client/lib/mailxapi.js +27 -5
  15. package/package.json +7 -7
  16. package/packages/mailx-imap/package-lock.json +2 -2
  17. package/packages/mailx-imap/package.json +1 -1
  18. package/packages/mailx-service/index.d.ts +5 -0
  19. package/packages/mailx-service/index.d.ts.map +1 -1
  20. package/packages/mailx-service/index.js +22 -1
  21. package/packages/mailx-service/index.js.map +1 -1
  22. package/packages/mailx-service/index.ts +25 -1
  23. package/packages/mailx-service/jsonrpc.js +4 -0
  24. package/packages/mailx-service/jsonrpc.js.map +1 -1
  25. package/packages/mailx-service/jsonrpc.ts +4 -0
  26. package/packages/mailx-service/package.json +1 -1
  27. package/packages/mailx-settings/index.d.ts +8 -1
  28. package/packages/mailx-settings/index.d.ts.map +1 -1
  29. package/packages/mailx-settings/index.js +71 -0
  30. package/packages/mailx-settings/index.js.map +1 -1
  31. package/packages/mailx-settings/index.ts +61 -1
  32. package/packages/mailx-settings/package.json +1 -1
  33. package/packages/mailx-store/package.json +1 -1
  34. package/packages/mailx-store-web/android-bootstrap.js +4 -0
  35. package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
  36. package/packages/mailx-store-web/android-bootstrap.ts +4 -0
  37. package/packages/mailx-store-web/package.json +1 -1
  38. package/packages/mailx-store-web/web-service.d.ts +4 -0
  39. package/packages/mailx-store-web/web-service.d.ts.map +1 -1
  40. package/packages/mailx-store-web/web-service.js +48 -2
  41. package/packages/mailx-store-web/web-service.js.map +1 -1
  42. package/packages/mailx-store-web/web-service.ts +41 -2
  43. package/packages/mailx-types/index.d.ts +2 -0
  44. package/packages/mailx-types/index.d.ts.map +1 -1
  45. package/packages/mailx-types/index.js +3 -0
  46. package/packages/mailx-types/index.js.map +1 -1
  47. package/packages/mailx-types/index.ts +7 -0
  48. package/packages/mailx-types/mailx-api.d.ts +14 -0
  49. package/packages/mailx-types/mailx-api.d.ts.map +1 -1
  50. package/packages/mailx-types/mailx-api.ts +5 -0
  51. package/packages/mailx-types/package.json +1 -1
  52. package/packages/mailx-types/reminder-state.d.ts +42 -0
  53. package/packages/mailx-types/reminder-state.d.ts.map +1 -0
  54. package/packages/mailx-types/reminder-state.js +85 -0
  55. package/packages/mailx-types/reminder-state.js.map +1 -0
  56. package/packages/mailx-types/reminder-state.ts +88 -0
  57. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-33656 → node_modules.npmglobalize-stash-7544}/.package-lock.json +0 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Reminder dismissed/snoozed state — shared across devices.
3
+ *
4
+ * The alarm subsystem (client/components/alarms.ts) keys every reminder by a
5
+ * per-occurrence string (`providerId:startMs[@offsetMs]` for calendar,
6
+ * `taskUuid:dueMs` for tasks). Those keys are built from Google-stable ids,
7
+ * so the SAME occurrence produces the SAME key on every device — which is
8
+ * what makes cross-device sync meaningful: dismiss a reminder on the desktop
9
+ * and the laptop's poller sees the dismissal before it fires its own popup.
10
+ *
11
+ * State lives in `reminders.jsonc` in the shared GDrive folder (same rails
12
+ * as allowlist.jsonc / userdict.csv). Values are epoch-ms timestamps:
13
+ * dismissed[key] = when the user dismissed it (retention: 30 days)
14
+ * snoozed[key] = fire again after this time (retention: 7 days past)
15
+ *
16
+ * Merge is per-key max — a dismissal or longer snooze on ANY device wins.
17
+ * Deletions don't propagate (union semantics); the only deletion in the
18
+ * alarm flow is the Open-button re-evaluate, which by construction touches
19
+ * a key that was never pushed as dismissed. Timestamps double as the prune
20
+ * criterion so the file can't grow forever.
21
+ *
22
+ * This module is dependency-free (mailx-types) so the desktop service
23
+ * (mailx-settings) and the Android/web service (mailx-store-web) share one
24
+ * merge implementation.
25
+ */
26
+ export const REMINDER_STATE_FILE = "reminders.jsonc";
27
+ /** Dismissed entries older than this are pruned — by then the occurrence is
28
+ * long past the alarm lookback window on every device. */
29
+ const DISMISSED_RETENTION_MS = 30 * 86400_000;
30
+ /** Snoozed entries whose until-time is this far past are pruned. */
31
+ const SNOOZED_RETENTION_MS = 7 * 86400_000;
32
+ /** Coerce arbitrary parsed JSON (or the client's localStorage maps, whose
33
+ * legacy dismissed values are boolean `true`) into a clean ReminderState.
34
+ * Legacy booleans become `now` so they get a full retention window. */
35
+ export function normalizeReminderState(raw, now) {
36
+ const out = { dismissed: {}, snoozed: {} };
37
+ if (raw && typeof raw === "object") {
38
+ for (const [k, v] of Object.entries(raw.dismissed || {})) {
39
+ if (v === true)
40
+ out.dismissed[k] = now;
41
+ else if (Number.isFinite(v) && v > 0)
42
+ out.dismissed[k] = v;
43
+ }
44
+ for (const [k, v] of Object.entries(raw.snoozed || {})) {
45
+ if (Number.isFinite(v) && v > 0)
46
+ out.snoozed[k] = v;
47
+ }
48
+ }
49
+ return out;
50
+ }
51
+ /** Union merge with per-key max, pruned by retention. Inputs must already
52
+ * be normalized. Pure — does not mutate its arguments. */
53
+ export function mergeReminderStates(a, b, now) {
54
+ const out = { dismissed: {}, snoozed: {} };
55
+ const dismissedCutoff = now - DISMISSED_RETENTION_MS;
56
+ const snoozedCutoff = now - SNOOZED_RETENTION_MS;
57
+ for (const src of [a.dismissed, b.dismissed]) {
58
+ for (const [k, v] of Object.entries(src)) {
59
+ if (v < dismissedCutoff)
60
+ continue;
61
+ if (!(k in out.dismissed) || v > out.dismissed[k])
62
+ out.dismissed[k] = v;
63
+ }
64
+ }
65
+ for (const src of [a.snoozed, b.snoozed]) {
66
+ for (const [k, v] of Object.entries(src)) {
67
+ if (v < snoozedCutoff)
68
+ continue;
69
+ if (!(k in out.snoozed) || v > out.snoozed[k])
70
+ out.snoozed[k] = v;
71
+ }
72
+ }
73
+ return out;
74
+ }
75
+ /** Deep-equal for change detection so a no-op merge skips the cloud write. */
76
+ export function reminderStatesEqual(a, b) {
77
+ const mapsEqual = (x, y) => {
78
+ const xk = Object.keys(x);
79
+ if (xk.length !== Object.keys(y).length)
80
+ return false;
81
+ return xk.every(k => y[k] === x[k]);
82
+ };
83
+ return mapsEqual(a.dismissed, b.dismissed) && mapsEqual(a.snoozed, b.snoozed);
84
+ }
85
+ //# sourceMappingURL=reminder-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reminder-state.js","sourceRoot":"","sources":["reminder-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Reminder dismissed/snoozed state — shared across devices.
3
+ *
4
+ * The alarm subsystem (client/components/alarms.ts) keys every reminder by a
5
+ * per-occurrence string (`providerId:startMs[@offsetMs]` for calendar,
6
+ * `taskUuid:dueMs` for tasks). Those keys are built from Google-stable ids,
7
+ * so the SAME occurrence produces the SAME key on every device — which is
8
+ * what makes cross-device sync meaningful: dismiss a reminder on the desktop
9
+ * and the laptop's poller sees the dismissal before it fires its own popup.
10
+ *
11
+ * State lives in `reminders.jsonc` in the shared GDrive folder (same rails
12
+ * as allowlist.jsonc / userdict.csv). Values are epoch-ms timestamps:
13
+ * dismissed[key] = when the user dismissed it (retention: 30 days)
14
+ * snoozed[key] = fire again after this time (retention: 7 days past)
15
+ *
16
+ * Merge is per-key max — a dismissal or longer snooze on ANY device wins.
17
+ * Deletions don't propagate (union semantics); the only deletion in the
18
+ * alarm flow is the Open-button re-evaluate, which by construction touches
19
+ * a key that was never pushed as dismissed. Timestamps double as the prune
20
+ * criterion so the file can't grow forever.
21
+ *
22
+ * This module is dependency-free (mailx-types) so the desktop service
23
+ * (mailx-settings) and the Android/web service (mailx-store-web) share one
24
+ * merge implementation.
25
+ */
26
+
27
+ export interface ReminderState {
28
+ /** occurrence-key → epoch-ms when dismissed */
29
+ dismissed: Record<string, number>;
30
+ /** occurrence-key → epoch-ms snooze-until */
31
+ snoozed: Record<string, number>;
32
+ }
33
+
34
+ export const REMINDER_STATE_FILE = "reminders.jsonc";
35
+
36
+ /** Dismissed entries older than this are pruned — by then the occurrence is
37
+ * long past the alarm lookback window on every device. */
38
+ const DISMISSED_RETENTION_MS = 30 * 86400_000;
39
+ /** Snoozed entries whose until-time is this far past are pruned. */
40
+ const SNOOZED_RETENTION_MS = 7 * 86400_000;
41
+
42
+ /** Coerce arbitrary parsed JSON (or the client's localStorage maps, whose
43
+ * legacy dismissed values are boolean `true`) into a clean ReminderState.
44
+ * Legacy booleans become `now` so they get a full retention window. */
45
+ export function normalizeReminderState(raw: any, now: number): ReminderState {
46
+ const out: ReminderState = { dismissed: {}, snoozed: {} };
47
+ if (raw && typeof raw === "object") {
48
+ for (const [k, v] of Object.entries(raw.dismissed || {})) {
49
+ if (v === true) out.dismissed[k] = now;
50
+ else if (Number.isFinite(v as number) && (v as number) > 0) out.dismissed[k] = v as number;
51
+ }
52
+ for (const [k, v] of Object.entries(raw.snoozed || {})) {
53
+ if (Number.isFinite(v as number) && (v as number) > 0) out.snoozed[k] = v as number;
54
+ }
55
+ }
56
+ return out;
57
+ }
58
+
59
+ /** Union merge with per-key max, pruned by retention. Inputs must already
60
+ * be normalized. Pure — does not mutate its arguments. */
61
+ export function mergeReminderStates(a: ReminderState, b: ReminderState, now: number): ReminderState {
62
+ const out: ReminderState = { dismissed: {}, snoozed: {} };
63
+ const dismissedCutoff = now - DISMISSED_RETENTION_MS;
64
+ const snoozedCutoff = now - SNOOZED_RETENTION_MS;
65
+ for (const src of [a.dismissed, b.dismissed]) {
66
+ for (const [k, v] of Object.entries(src)) {
67
+ if (v < dismissedCutoff) continue;
68
+ if (!(k in out.dismissed) || v > out.dismissed[k]) out.dismissed[k] = v;
69
+ }
70
+ }
71
+ for (const src of [a.snoozed, b.snoozed]) {
72
+ for (const [k, v] of Object.entries(src)) {
73
+ if (v < snoozedCutoff) continue;
74
+ if (!(k in out.snoozed) || v > out.snoozed[k]) out.snoozed[k] = v;
75
+ }
76
+ }
77
+ return out;
78
+ }
79
+
80
+ /** Deep-equal for change detection so a no-op merge skips the cloud write. */
81
+ export function reminderStatesEqual(a: ReminderState, b: ReminderState): boolean {
82
+ const mapsEqual = (x: Record<string, number>, y: Record<string, number>): boolean => {
83
+ const xk = Object.keys(x);
84
+ if (xk.length !== Object.keys(y).length) return false;
85
+ return xk.every(k => y[k] === x[k]);
86
+ };
87
+ return mapsEqual(a.dismissed, b.dismissed) && mapsEqual(a.snoozed, b.snoozed);
88
+ }