proton-mail-bridge-client 2.3.2 → 2.4.0

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 (68) hide show
  1. package/README.md +8 -2
  2. package/dist/cli.d.ts +11 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +348 -202
  5. package/dist/cli.js.map +1 -1
  6. package/dist/index.d.ts +10 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +251 -133
  9. package/dist/index.js.map +1 -1
  10. package/dist/scripts/check-claude-desktop.d.ts +18 -0
  11. package/dist/scripts/check-claude-desktop.d.ts.map +1 -1
  12. package/dist/scripts/check-claude-desktop.js +91 -30
  13. package/dist/scripts/check-claude-desktop.js.map +1 -1
  14. package/dist/scripts/install-claude-desktop.d.ts +12 -0
  15. package/dist/scripts/install-claude-desktop.d.ts.map +1 -1
  16. package/dist/scripts/install-claude-desktop.js +211 -43
  17. package/dist/scripts/install-claude-desktop.js.map +1 -1
  18. package/dist/services/analytics-service.d.ts +1 -1
  19. package/dist/services/analytics-service.d.ts.map +1 -1
  20. package/dist/services/analytics-service.js +2 -2
  21. package/dist/services/analytics-service.js.map +1 -1
  22. package/dist/services/audit-service.d.ts.map +1 -1
  23. package/dist/services/audit-service.js +7 -2
  24. package/dist/services/audit-service.js.map +1 -1
  25. package/dist/services/background-sync-service.d.ts.map +1 -1
  26. package/dist/services/background-sync-service.js +10 -2
  27. package/dist/services/background-sync-service.js.map +1 -1
  28. package/dist/services/delivery-queue-service.d.ts.map +1 -1
  29. package/dist/services/delivery-queue-service.js +3 -8
  30. package/dist/services/delivery-queue-service.js.map +1 -1
  31. package/dist/services/draft-store-service.d.ts.map +1 -1
  32. package/dist/services/draft-store-service.js +3 -4
  33. package/dist/services/draft-store-service.js.map +1 -1
  34. package/dist/services/local-index-service.d.ts +2 -1
  35. package/dist/services/local-index-service.d.ts.map +1 -1
  36. package/dist/services/local-index-service.js +45 -9
  37. package/dist/services/local-index-service.js.map +1 -1
  38. package/dist/services/simple-imap-service.d.ts +21 -2
  39. package/dist/services/simple-imap-service.d.ts.map +1 -1
  40. package/dist/services/simple-imap-service.js +271 -103
  41. package/dist/services/simple-imap-service.js.map +1 -1
  42. package/dist/services/smtp-service.d.ts.map +1 -1
  43. package/dist/services/smtp-service.js +15 -0
  44. package/dist/services/smtp-service.js.map +1 -1
  45. package/dist/services/snooze-service.d.ts.map +1 -1
  46. package/dist/services/snooze-service.js +40 -20
  47. package/dist/services/snooze-service.js.map +1 -1
  48. package/dist/services/template-service.d.ts +4 -2
  49. package/dist/services/template-service.d.ts.map +1 -1
  50. package/dist/services/template-service.js +43 -10
  51. package/dist/services/template-service.js.map +1 -1
  52. package/dist/types/index.d.ts +1 -0
  53. package/dist/types/index.d.ts.map +1 -1
  54. package/dist/utils/atomic-write.d.ts +2 -0
  55. package/dist/utils/atomic-write.d.ts.map +1 -0
  56. package/dist/utils/atomic-write.js +36 -0
  57. package/dist/utils/atomic-write.js.map +1 -0
  58. package/dist/utils/file-lock.d.ts.map +1 -1
  59. package/dist/utils/file-lock.js +39 -4
  60. package/dist/utils/file-lock.js.map +1 -1
  61. package/dist/utils/helpers.d.ts +14 -0
  62. package/dist/utils/helpers.d.ts.map +1 -1
  63. package/dist/utils/helpers.js +222 -21
  64. package/dist/utils/helpers.js.map +1 -1
  65. package/dist/utils/logger.d.ts.map +1 -1
  66. package/dist/utils/logger.js +29 -12
  67. package/dist/utils/logger.js.map +1 -1
  68. package/package.json +1 -1
@@ -23,6 +23,39 @@ const LOCK_ACQUIRE_TIMEOUT_MS = 10_000;
23
23
  // instead of blocking forever. Generous relative to how briefly these
24
24
  // load-modify-save cycles actually take.
25
25
  const STALE_LOCK_MS = 30_000;
26
+ // Taking over a stale lock is itself a critical section. Two waiters can both judge the same lock stale; if
27
+ // each then removes "the" lock and creates its own, the second removes the first's brand-new lock and both
28
+ // end up inside the protected section. So stealing is serialized behind a short-lived guard file, and the
29
+ // staleness verdict is taken again while holding it: a waiter that arrives second sees the first stealer's
30
+ // fresh lock (live PID) and backs off.
31
+ const STEAL_GUARD_STALE_MS = 5_000;
32
+ async function stealStale(lockPath) {
33
+ const guard = `${lockPath}.steal`;
34
+ let handle;
35
+ try {
36
+ handle = await open(guard, "wx");
37
+ }
38
+ catch (error) {
39
+ if (error.code !== "EEXIST")
40
+ throw error;
41
+ // Someone else is stealing right now. A guard left behind by a process that died mid-steal is removed
42
+ // once it is old; either way this waiter just loops and looks again.
43
+ const info = await stat(guard).catch(() => undefined);
44
+ if (info && Date.now() - info.mtimeMs > STEAL_GUARD_STALE_MS)
45
+ await unlink(guard).catch(() => undefined);
46
+ return;
47
+ }
48
+ try {
49
+ await handle.close();
50
+ if (await isStale(lockPath)) {
51
+ logger.warn(`Stale lock file detected, stealing it: ${lockPath}`, "FileLock");
52
+ await unlink(lockPath).catch(() => undefined);
53
+ }
54
+ }
55
+ finally {
56
+ await unlink(guard).catch(() => undefined);
57
+ }
58
+ }
26
59
  function lockPathFor(storePath) {
27
60
  return `${storePath}.lock`;
28
61
  }
@@ -123,12 +156,14 @@ async function acquire(lockPath) {
123
156
  }
124
157
  catch (error) {
125
158
  const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
126
- if (code !== "EEXIST") {
159
+ // Windows refuses to open a lock file that another process has just deleted (delete pending) with
160
+ // EPERM/EACCES/EBUSY instead of letting the create succeed; that is contention, not a real failure.
161
+ const contendedOnWindows = process.platform === "win32" && (code === "EPERM" || code === "EACCES" || code === "EBUSY");
162
+ if (code !== "EEXIST" && !contendedOnWindows) {
127
163
  throw error;
128
164
  }
129
- if (await isStale(lockPath)) {
130
- logger.warn(`Stale lock file detected, stealing it: ${lockPath}`, "FileLock");
131
- await unlink(lockPath).catch(() => undefined);
165
+ if (!contendedOnWindows && (await isStale(lockPath))) {
166
+ await stealStale(lockPath);
132
167
  continue;
133
168
  }
134
169
  if (Date.now() > deadline) {
@@ -1 +1 @@
1
- {"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../../src/utils/file-lock.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../../src/utils/file-lock.ts"],"names":[],"mappings":"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"}
@@ -31,7 +31,13 @@ export declare function extractMessageIdList(value?: string | string[]): string[
31
31
  export declare function extractAttachments(structure?: MessageStructureObject): EmailAttachmentSummary[];
32
32
  export declare function dedupeEmails(emails: EmailSummary[]): EmailSummary[];
33
33
  export declare function sortEmailsByNewest<T extends Pick<EmailSummary, "date" | "internalDate" | "uid">>(emails: T[]): T[];
34
+ export declare class InvalidArgumentError extends Error {
35
+ constructor(message: string);
36
+ }
34
37
  export declare function normalizeLimit(value: unknown, defaultValue: number, min?: number, max?: number): number;
38
+ export declare function optionalInteger(value: unknown, min: number, max: number): number | undefined;
39
+ export declare function optionalNumber(value: unknown, min: number, max: number): number | undefined;
40
+ export declare function optionalBoolean(value: unknown): boolean | undefined;
35
41
  export declare function normalizeBoolean(value: unknown, defaultValue: boolean): boolean;
36
42
  export declare function parseDateInput(value?: string): Date | undefined;
37
43
  export declare function nextDay(date: Date): Date;
@@ -54,10 +60,18 @@ export declare function stringifyForJson(value: unknown): string;
54
60
  export declare function normalizeJsonValue(value: unknown): unknown;
55
61
  export declare function lowerCaseAddress(value?: string): string | undefined;
56
62
  export declare function isSelfAddress(candidate: string | undefined, ownerEmail: string | undefined): boolean;
63
+ export declare function isOutgoingMessage(message: {
64
+ from: Array<{
65
+ address?: string;
66
+ }>;
67
+ folder?: string;
68
+ }, ownerEmail?: string): boolean;
57
69
  export declare function normalizeMessageId(value?: string): string | undefined;
58
70
  export declare function extractDomain(address: string): string | undefined;
59
71
  export declare function normalizeSubjectForThread(subject: string): string;
60
72
  export declare function sanitizeFileName(filename?: string, fallback?: string): string;
73
+ export declare function isPathInside(parent: string, child: string, separator?: string): boolean;
74
+ export declare function decodeAttachmentText(content: Buffer, declaredCharset?: string): string | undefined;
61
75
  export declare function isTextLikeMimeType(mimeType?: string): boolean;
62
76
  export declare function classifyAttachment(input: {
63
77
  filename?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/utils/helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/utils/helpers.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { sep as pathSeparator } from "node:path";
2
3
  import TurndownService from "turndown";
3
4
  const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
4
5
  const EMAIL_ID_SEPARATOR = "::";
@@ -435,11 +436,34 @@ export function sortEmailsByNewest(emails) {
435
436
  return right.uid - left.uid;
436
437
  });
437
438
  }
439
+ // A tool argument that could not be read as what it should be. The server maps it to InvalidParams, so
440
+ // the caller is told, instead of the argument being ignored and a default used.
441
+ export class InvalidArgumentError extends Error {
442
+ constructor(message) {
443
+ super(message);
444
+ this.name = "InvalidArgumentError";
445
+ }
446
+ }
447
+ // Reads an integer argument: a number, or a string that holds one ("5"), clamped to [min, max]. Nothing given
448
+ // (undefined, null, "") uses the default. Anything else is an error rather than a silent default.
438
449
  export function normalizeLimit(value, defaultValue, min = 1, max = 250) {
439
- if (typeof value !== "number" || Number.isNaN(value)) {
450
+ if (value === undefined || value === null || value === "") {
440
451
  return defaultValue;
441
452
  }
442
- const rounded = Math.trunc(value);
453
+ let number;
454
+ if (typeof value === "number") {
455
+ number = value;
456
+ }
457
+ else if (typeof value === "string" && /^\s*-?\d+(\.\d+)?\s*$/.test(value)) {
458
+ number = Number(value);
459
+ }
460
+ else {
461
+ throw new InvalidArgumentError(`Expected a number but got ${JSON.stringify(value)}.`);
462
+ }
463
+ if (Number.isNaN(number)) {
464
+ throw new InvalidArgumentError("Expected a number but got NaN.");
465
+ }
466
+ const rounded = Math.trunc(number);
443
467
  if (rounded < min) {
444
468
  return min;
445
469
  }
@@ -448,8 +472,58 @@ export function normalizeLimit(value, defaultValue, min = 1, max = 250) {
448
472
  }
449
473
  return rounded;
450
474
  }
475
+ // Like normalizeLimit, but "nothing given" stays undefined, for arguments whose default lives in the service.
476
+ export function optionalInteger(value, min, max) {
477
+ return value === undefined || value === null || value === "" ? undefined : normalizeLimit(value, min, min, max);
478
+ }
479
+ // A number argument that may be absent (a filter such as sizeLarger): absent stays undefined, "1.5" and 1.5
480
+ // both read as 1.5, anything unreadable is an error, and the value is kept inside [min, max].
481
+ export function optionalNumber(value, min, max) {
482
+ if (value === undefined || value === null || value === "")
483
+ return undefined;
484
+ let number;
485
+ if (typeof value === "number") {
486
+ number = value;
487
+ }
488
+ else if (typeof value === "string" && /^\s*-?\d+(\.\d+)?\s*$/.test(value)) {
489
+ number = Number(value);
490
+ }
491
+ else {
492
+ throw new InvalidArgumentError(`Expected a number but got ${JSON.stringify(value)}.`);
493
+ }
494
+ if (Number.isNaN(number))
495
+ throw new InvalidArgumentError("Expected a number but got NaN.");
496
+ return Math.min(max, Math.max(min, number));
497
+ }
498
+ const TRUE_WORDS = new Set(["true", "yes", "1", "on"]);
499
+ const FALSE_WORDS = new Set(["false", "no", "0", "off"]);
500
+ // Reads a boolean argument. Clients and models do send "true" and 1; reading only a real boolean made
501
+ // dryRun:"true" run for real and isRead:"false" mark a message read. Nothing given uses the default; a value
502
+ // that is none of the recognised spellings is an error, never a silent default.
503
+ export function optionalBoolean(value) {
504
+ return value === undefined || value === null || value === "" ? undefined : normalizeBoolean(value, false);
505
+ }
451
506
  export function normalizeBoolean(value, defaultValue) {
452
- return typeof value === "boolean" ? value : defaultValue;
507
+ if (typeof value === "boolean")
508
+ return value;
509
+ if (value === undefined || value === null)
510
+ return defaultValue;
511
+ if (typeof value === "number") {
512
+ if (value === 1)
513
+ return true;
514
+ if (value === 0)
515
+ return false;
516
+ }
517
+ else if (typeof value === "string") {
518
+ const word = value.trim().toLowerCase();
519
+ if (word === "")
520
+ return defaultValue;
521
+ if (TRUE_WORDS.has(word))
522
+ return true;
523
+ if (FALSE_WORDS.has(word))
524
+ return false;
525
+ }
526
+ throw new InvalidArgumentError(`Expected true or false but got ${JSON.stringify(value)}.`);
453
527
  }
454
528
  export function parseDateInput(value) {
455
529
  if (!value) {
@@ -474,8 +548,13 @@ const FOLD_EXTRA = { ß: "ss", æ: "ae", œ: "oe", ø: "o", ł: "l", đ: "d", ð
474
548
  export function hasNonAscii(value) {
475
549
  return value !== undefined && /[^\x00-\x7f]/.test(value);
476
550
  }
551
+ const NON_ASCII = /[^\x00-\x7f]/;
477
552
  // Lowercase, strip combining accents, and spell out the letters Unicode does not decompose.
478
553
  export function foldSearchText(value) {
554
+ // Plain ASCII (most of the text the index compares) folds to just its lower case: skip the Unicode
555
+ // normalisation, which is most of the cost when this runs for every row of a large mailbox.
556
+ if (!NON_ASCII.test(value))
557
+ return value.toLowerCase();
479
558
  return value
480
559
  .normalize("NFKD")
481
560
  .replace(/[\u0300-\u036f]/g, "")
@@ -487,6 +566,8 @@ export function foldSearchText(value) {
487
566
  const SPELLED_OUT = { ä: "ae", ö: "oe", ü: "ue", ø: "oe", å: "aa" };
488
567
  // The value with those letters spelled out as pairs, or undefined when it has none.
489
568
  function spelledOutText(value) {
569
+ if (!NON_ASCII.test(value))
570
+ return undefined;
490
571
  const lower = value.normalize("NFC").toLowerCase();
491
572
  if (!/[äöüøå]/.test(lower))
492
573
  return undefined;
@@ -698,6 +779,16 @@ export function isSelfAddress(candidate, ownerEmail) {
698
779
  }
699
780
  return stripPlusTag(a) === stripPlusTag(b);
700
781
  }
782
+ // Whether a message was written by the mailbox owner. The login address is compared the way every other
783
+ // "is this me" check here does (case-insensitive, a +tag ignored), and anything in the Sent folder counts too: a
784
+ // reply sent from an alias (alias@pm.me) is the owner's, but its address is not the login address.
785
+ export function isOutgoingMessage(message, ownerEmail) {
786
+ if (normalizeMailboxLabel(message.folder) === "Sent")
787
+ return true;
788
+ if (!ownerEmail)
789
+ return false;
790
+ return message.from.some((entry) => isSelfAddress(entry.address, ownerEmail));
791
+ }
701
792
  export function normalizeMessageId(value) {
702
793
  const trimmed = value?.trim();
703
794
  if (!trimmed) {
@@ -716,10 +807,87 @@ export function normalizeSubjectForThread(subject) {
716
807
  }
717
808
  return collapsed.replace(/^(?:(?:re|fw|fwd)\s*:\s*)+/i, "").trim() || "(no subject)";
718
809
  }
810
+ const WINDOWS_RESERVED_NAME = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
811
+ // Well under the 255-byte limit of common filesystems, leaving room for a " (n)" suffix on a collision.
812
+ const MAX_FILE_NAME_BYTES = 200;
813
+ function cleanFileName(value) {
814
+ return value
815
+ .normalize("NFC")
816
+ .replace(/[\u0000-\u001f\u007f]/g, "")
817
+ .replace(/[\\/:*?"<>|]+/g, "_")
818
+ .replace(/\.\./g, "_")
819
+ .trim()
820
+ .replace(/[. ]+$/g, "");
821
+ }
822
+ // A file name taken from untrusted mail made safe to create on any platform: no path separators or control
823
+ // characters, no trailing dots or spaces (Windows drops them, so two names could collapse into one), no
824
+ // Windows device names (CON, NUL, COM1...), never "." or "..", and short enough for the filesystem.
719
825
  export function sanitizeFileName(filename, fallback = "attachment") {
720
- let name = (filename || fallback).normalize('NFC');
721
- const normalized = name.replace(/[\\/:*?"<>|]+/g, "_").replace(/\.\./g, '_').trim();
722
- return normalized || fallback;
826
+ // "." and ".." (and a name of only dots and spaces) are directory references, not file names.
827
+ let name = /^[\s.]*$/.test(filename ?? "") ? "" : cleanFileName(filename ?? "");
828
+ if (!name) {
829
+ name = cleanFileName(fallback) || "attachment";
830
+ }
831
+ if (WINDOWS_RESERVED_NAME.test(name.split(".")[0])) {
832
+ name = `_${name}`;
833
+ }
834
+ if (Buffer.byteLength(name) > MAX_FILE_NAME_BYTES) {
835
+ const dot = name.lastIndexOf(".");
836
+ const extension = dot > 0 && Buffer.byteLength(name.slice(dot)) <= 16 ? name.slice(dot) : "";
837
+ let stem = Array.from(extension ? name.slice(0, dot) : name);
838
+ while (stem.length > 1 && Buffer.byteLength(stem.join("")) + Buffer.byteLength(extension) > MAX_FILE_NAME_BYTES) {
839
+ stem = stem.slice(0, -1);
840
+ }
841
+ name = `${stem.join("")}${extension}`;
842
+ }
843
+ return name;
844
+ }
845
+ // Whether `child` is `parent` or lies below it. Appending the separator to a parent that already ends in one
846
+ // (a filesystem root: "/" or "C:\\") would give "//" and refuse every path.
847
+ export function isPathInside(parent, child, separator = pathSeparator) {
848
+ if (child === parent)
849
+ return true;
850
+ return child.startsWith(parent.endsWith(separator) ? parent : parent + separator);
851
+ }
852
+ // Decodes the bytes of a text attachment. The charset the sender declared is honoured (windows-1250,
853
+ // iso-8859-2... are common in mail from Poland and Czechia), a byte order mark is read and removed, and data
854
+ // that is really binary (NUL bytes, or mostly bytes that are not valid text) is not returned as text.
855
+ export function decodeAttachmentText(content, declaredCharset) {
856
+ if (content.length === 0)
857
+ return undefined;
858
+ let label = (declaredCharset ?? "").trim().toLowerCase().replace(/^["']|["']$/g, "");
859
+ let bytes = content;
860
+ if (content.length >= 3 && content[0] === 0xef && content[1] === 0xbb && content[2] === 0xbf) {
861
+ label = "utf-8";
862
+ bytes = content.subarray(3);
863
+ }
864
+ else if (content.length >= 2 && content[0] === 0xff && content[1] === 0xfe) {
865
+ label = "utf-16le";
866
+ bytes = content.subarray(2);
867
+ }
868
+ else if (content.length >= 2 && content[0] === 0xfe && content[1] === 0xff) {
869
+ label = "utf-16be";
870
+ bytes = content.subarray(2);
871
+ }
872
+ else if (bytes.subarray(0, 8192).includes(0)) {
873
+ return undefined;
874
+ }
875
+ let decoder;
876
+ try {
877
+ decoder = new TextDecoder(label || "utf-8");
878
+ }
879
+ catch {
880
+ decoder = new TextDecoder("utf-8");
881
+ }
882
+ const text = decoder.decode(bytes);
883
+ let replacements = 0;
884
+ for (let index = 0; index < text.length; index += 1) {
885
+ if (text.charCodeAt(index) === 0xfffd)
886
+ replacements += 1;
887
+ }
888
+ if (text.length > 0 && replacements / text.length > 0.1)
889
+ return undefined;
890
+ return text;
723
891
  }
724
892
  export function isTextLikeMimeType(mimeType) {
725
893
  if (!mimeType) {
@@ -776,21 +944,49 @@ export function classifyAttachment(input) {
776
944
  }
777
945
  return { kind: "other", isCalendarInvite: false, isSignature: false };
778
946
  }
947
+ // Splits "NAME;PARAM=x:value" at the first colon that is not inside a quoted parameter value
948
+ // (ORGANIZER;CN="Doe: John":mailto:j@x.com), returning the upper-case name and the value.
949
+ function splitIcsProperty(line) {
950
+ let quoted = false;
951
+ for (let index = 0; index < line.length; index += 1) {
952
+ const char = line[index];
953
+ if (char === '"') {
954
+ quoted = !quoted;
955
+ }
956
+ else if (char === ":" && !quoted) {
957
+ if (index === 0)
958
+ return undefined;
959
+ return { name: line.slice(0, index).split(";")[0].trim().toUpperCase(), value: line.slice(index + 1).trim() };
960
+ }
961
+ }
962
+ return undefined;
963
+ }
964
+ // RFC 5545 text escapes: \n is a line break, \, \; \\ are the literal character.
965
+ function unescapeIcsText(value) {
966
+ return value.replace(/\\([nN,;\\])/g, (_match, char) => (char === "n" || char === "N" ? " " : char));
967
+ }
968
+ const ICS_TEXT_FIELDS = new Set(["SUMMARY", "LOCATION", "DESCRIPTION"]);
969
+ // Components whose own properties (e.g. the DTSTART of a daylight-saving rule) are not the event's.
970
+ const ICS_NON_EVENT_COMPONENTS = new Set(["VTIMEZONE", "DAYLIGHT", "STANDARD", "VALARM"]);
779
971
  export function summarizeCalendarText(value) {
780
- const normalized = value.replace(/\r/g, "");
972
+ const normalized = value.replace(/\r\n?/g, "\n");
973
+ // A line that starts with a space or tab continues the previous one (RFC 5545 line folding).
974
+ const lines = normalized.replace(/\n[ \t]/g, "").split("\n");
781
975
  const fields = new Map();
782
- const lines = normalized.split("\n");
783
- // Only read the first VEVENT's own properties when one exists: a VTIMEZONE ahead of it
784
- // carries its own DTSTART (e.g. 19700329T020000 for a DST rule), and a nested VALARM
785
- // carries its own SUMMARY/DESCRIPTION.
786
- const hasEvent = lines.some((line) => line.trim().toUpperCase() === "BEGIN:VEVENT");
787
976
  const componentStack = [];
977
+ let method;
978
+ let eventCount = 0;
788
979
  let seenEvent = false;
980
+ // Only the first VEVENT's own properties are read when there is one: a VTIMEZONE ahead of it carries
981
+ // its own DTSTART (e.g. 19700329T020000 for a DST rule) and a nested VALARM its own SUMMARY.
982
+ const hasEvent = lines.some((line) => line.trim().toUpperCase() === "BEGIN:VEVENT");
789
983
  for (const line of lines) {
790
984
  const marker = /^(BEGIN|END):(\S+)\s*$/i.exec(line.trim());
791
985
  if (marker) {
792
986
  const component = marker[2].toUpperCase();
793
987
  if (marker[1].toUpperCase() === "BEGIN") {
988
+ if (component === "VEVENT")
989
+ eventCount += 1;
794
990
  componentStack.push(component);
795
991
  }
796
992
  else {
@@ -801,25 +997,30 @@ export function summarizeCalendarText(value) {
801
997
  }
802
998
  continue;
803
999
  }
804
- if (hasEvent && (seenEvent || componentStack[componentStack.length - 1] !== "VEVENT")) {
1000
+ const property = splitIcsProperty(line);
1001
+ if (!property)
805
1002
  continue;
806
- }
807
- const separator = line.indexOf(":");
808
- if (separator <= 0) {
1003
+ const top = componentStack[componentStack.length - 1];
1004
+ if (top === "VCALENDAR" && property.name === "METHOD") {
1005
+ method = property.value.toUpperCase();
809
1006
  continue;
810
1007
  }
811
- const key = line.slice(0, separator).split(";")[0].trim().toUpperCase();
812
- const fieldValue = line.slice(separator + 1).trim();
813
- if (fieldValue && !fields.has(key)) {
814
- fields.set(key, fieldValue);
1008
+ if (componentStack.some((component) => ICS_NON_EVENT_COMPONENTS.has(component)))
1009
+ continue;
1010
+ if (hasEvent && (seenEvent || top !== "VEVENT"))
1011
+ continue;
1012
+ if (property.value && !fields.has(property.name)) {
1013
+ fields.set(property.name, ICS_TEXT_FIELDS.has(property.name) ? unescapeIcsText(property.value) : property.value);
815
1014
  }
816
1015
  }
1016
+ const cancelled = method === "CANCEL" || fields.get("STATUS")?.toUpperCase() === "CANCELLED";
817
1017
  const summary = [
818
- fields.get("SUMMARY"),
1018
+ fields.get("SUMMARY") ? `${cancelled ? "[Cancelled] " : ""}${fields.get("SUMMARY")}` : cancelled ? "[Cancelled]" : undefined,
819
1019
  fields.get("ORGANIZER"),
820
1020
  fields.get("DTSTART") ? `Starts ${fields.get("DTSTART")}` : undefined,
821
1021
  fields.get("DTEND") ? `Ends ${fields.get("DTEND")}` : undefined,
822
1022
  fields.get("LOCATION") ? `Location ${fields.get("LOCATION")}` : undefined,
1023
+ eventCount > 1 ? `+${eventCount - 1} more event${eventCount === 2 ? "" : "s"}` : undefined,
823
1024
  ]
824
1025
  .filter(Boolean)
825
1026
  .join(" | ");