email-origin-chain 1.0.7 → 1.0.10

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.
@@ -30,10 +30,15 @@ class CrispDetector {
30
30
  : from
31
31
  ? { name: from.name || '', address: from.address || '' }
32
32
  : '';
33
+ const to = result.email.to;
34
+ const toValue = Array.isArray(to) && to.length > 0
35
+ ? (typeof to[0] === 'string' ? to[0] : { name: to[0].name || '', address: to[0].address || '' })
36
+ : (typeof to === 'string' ? to : undefined);
33
37
  return {
34
38
  found: true,
35
39
  email: {
36
40
  from: fromValue,
41
+ to: toValue,
37
42
  subject: result.email.subject || undefined,
38
43
  date: result.email.date || undefined,
39
44
  body: result.email.body || undefined
@@ -100,6 +100,7 @@ class NewOutlookDetector {
100
100
  found: true,
101
101
  email: {
102
102
  from: address ? { name: name.replace(/["']/g, ''), address: address } : name,
103
+ to: to ? to.value : undefined,
103
104
  subject: subject.value,
104
105
  date: date ? date.value : undefined,
105
106
  body: finalBody
@@ -10,7 +10,7 @@ import { ForwardDetector, DetectionResult } from './types';
10
10
  */
11
11
  export declare class OutlookEmptyHeaderDetector implements ForwardDetector {
12
12
  readonly name = "outlook_empty_header";
13
- readonly priority = 50;
13
+ readonly priority = -50;
14
14
  private readonly HEADER_PATTERN;
15
15
  detect(text: string): DetectionResult;
16
16
  }
@@ -14,7 +14,7 @@ const cleaner_1 = require("../utils/cleaner");
14
14
  class OutlookEmptyHeaderDetector {
15
15
  constructor() {
16
16
  this.name = 'outlook_empty_header';
17
- this.priority = 50; // Fallback for corrupted headers (after specifics, before generic Crisp)
17
+ this.priority = -50; // Very specific - High Priority
18
18
  // Regex to capture the header block:
19
19
  // 1. Optional Separator (mostly underscores)
20
20
  // 2. De: ... (From)
@@ -51,6 +51,7 @@ class OutlookEmptyHeaderDetector {
51
51
  message: message || undefined,
52
52
  email: {
53
53
  from: fromLine,
54
+ to: toLine,
54
55
  subject: subjectLine,
55
56
  date: dateLine || undefined,
56
57
  body: finalBody
@@ -107,6 +107,7 @@ class OutlookFRDetector {
107
107
  from: fromEmail.includes('@')
108
108
  ? { name: fromName !== fromEmail ? fromName : '', address: fromEmail }
109
109
  : { name: fromName, address: fromName },
110
+ to: a ? extractValue(a.line) : undefined,
110
111
  subject,
111
112
  date: dateRaw,
112
113
  body: finalBody
@@ -4,7 +4,7 @@ import { ForwardDetector, DetectionResult } from './types';
4
4
  */
5
5
  export declare class OutlookReverseFrDetector implements ForwardDetector {
6
6
  readonly name = "outlook_reverse_fr";
7
- readonly priority = -20;
7
+ readonly priority = -45;
8
8
  private readonly ENVOYE_PATTERN;
9
9
  private readonly DE_PATTERN;
10
10
  private readonly A_PATTERN;
@@ -8,7 +8,7 @@ const cleaner_1 = require("../utils/cleaner");
8
8
  class OutlookReverseFrDetector {
9
9
  constructor() {
10
10
  this.name = 'outlook_reverse_fr';
11
- this.priority = -20; // Specific detector - High Priority (Override)
11
+ this.priority = -45; // Specific detector - High Priority
12
12
  // Regex patterns for field detection
13
13
  this.ENVOYE_PATTERN = /^[ \t]*Envoy(?:é|=E9|e)?\s*:\s*(.*?)\s*$/m;
14
14
  this.DE_PATTERN = /^[ \t]*De\s*:/i;
@@ -76,6 +76,7 @@ class OutlookReverseFrDetector {
76
76
  from: fromEmail.includes('@')
77
77
  ? { name: fromName !== fromEmail ? fromName : '', address: fromEmail }
78
78
  : { name: fromName, address: fromName },
79
+ to: a ? extractValue(a.line) : undefined,
79
80
  subject: objet ? extractValue(objet.line) : '',
80
81
  date: extractValue(envoyeMatch[0]),
81
82
  body: finalBody
@@ -15,12 +15,12 @@ class DetectorRegistry {
15
15
  constructor(customDetectors = []) {
16
16
  this.detectors = [];
17
17
  // Register all detectors (priority determines order)
18
- this.register(new crisp_detector_1.CrispDetector()); // priority: 0 (highest - universal library)
19
- this.register(new outlook_empty_header_detector_1.OutlookEmptyHeaderDetector()); // priority: 5 (handle empty headers)
20
- this.register(new outlook_reverse_fr_detector_1.OutlookReverseFrDetector()); // priority: 6 (handle reversed FR headers)
21
- this.register(new reply_detector_1.ReplyDetector()); // priority: 7 (handle standard replies)
22
- this.register(new outlook_fr_detector_1.OutlookFRDetector()); // priority: 10 (fallback for FR formats)
23
- this.register(new new_outlook_detector_1.NewOutlookDetector()); // priority: 10 (fallback for new Outlook)
18
+ this.register(new outlook_empty_header_detector_1.OutlookEmptyHeaderDetector()); // priority: -50 (Very specific)
19
+ this.register(new outlook_reverse_fr_detector_1.OutlookReverseFrDetector()); // priority: -45 (Specific)
20
+ this.register(new new_outlook_detector_1.NewOutlookDetector()); // priority: -40 (Specific)
21
+ this.register(new outlook_fr_detector_1.OutlookFRDetector()); // priority: -30 (Fallback for FR)
22
+ this.register(new reply_detector_1.ReplyDetector()); // priority: -10 (Replies)
23
+ this.register(new crisp_detector_1.CrispDetector()); // priority: 100 (Universal fallback)
24
24
  // Register custom detectors
25
25
  customDetectors.forEach(detector => this.register(detector));
26
26
  }
@@ -10,6 +10,10 @@ export interface DetectionResult {
10
10
  name: string;
11
11
  address: string;
12
12
  };
13
+ to?: string | {
14
+ name: string;
15
+ address: string;
16
+ };
13
17
  subject?: string;
14
18
  date?: string;
15
19
  body?: string;
package/dist/index.js CHANGED
@@ -53,15 +53,19 @@ async function extractDeepestHybrid(raw, options) {
53
53
  const inlineResult = await (0, inline_layer_1.processInline)(mimeResult.rawBody, mimeResult.depth, mimeResult.history, opts.customDetectors);
54
54
  // Step 3: Align results
55
55
  let from = (0, utils_1.normalizeFrom)(inlineResult.from);
56
+ let to = (0, utils_1.normalizeFrom)(inlineResult.to);
56
57
  let subject = inlineResult.subject;
57
58
  let date_raw = inlineResult.date_raw;
58
59
  let date_iso = inlineResult.date_iso;
59
60
  let text = inlineResult.text;
60
61
  if (inlineResult.diagnostics.method === 'fallback' && mimeResult.metadata) {
61
62
  const m = mimeResult.metadata;
62
- if (!from && m.from?.value?.[0]) {
63
+ if ((!from || !from.address) && m.from?.value?.[0]) {
63
64
  from = (0, utils_1.normalizeFrom)({ name: m.from.value[0].name, address: m.from.value[0].address });
64
65
  }
66
+ if ((!to || !to.address) && m.to?.value?.[0]) {
67
+ to = (0, utils_1.normalizeFrom)({ name: m.to.value[0].name, address: m.to.value[0].address });
68
+ }
65
69
  if (!subject && m.subject)
66
70
  subject = m.subject;
67
71
  if (!date_iso && m.date)
@@ -79,6 +83,9 @@ async function extractDeepestHybrid(raw, options) {
79
83
  if (m.from?.value?.[0]) {
80
84
  rootInHistory.from = (0, utils_1.normalizeFrom)({ name: m.from.value[0].name, address: m.from.value[0].address });
81
85
  }
86
+ if (m.to?.value?.[0]) {
87
+ rootInHistory.to = (0, utils_1.normalizeFrom)({ name: m.to.value[0].name, address: m.to.value[0].address });
88
+ }
82
89
  if (m.subject)
83
90
  rootInHistory.subject = m.subject;
84
91
  }
@@ -96,6 +103,7 @@ async function extractDeepestHybrid(raw, options) {
96
103
  ...restInlineResult,
97
104
  // Use our normalized/enriched values
98
105
  from,
106
+ to,
99
107
  subject,
100
108
  date_raw,
101
109
  date_iso,
@@ -115,6 +123,7 @@ async function extractDeepestHybrid(raw, options) {
115
123
  catch (error) {
116
124
  return {
117
125
  from: null,
126
+ to: null,
118
127
  subject: null,
119
128
  date_raw: null,
120
129
  date_iso: null,
@@ -19,6 +19,7 @@ async function processInline(text, depth, baseHistory = [], customDetectors = []
19
19
  if (history.length === 0) {
20
20
  history.push({
21
21
  from: null,
22
+ to: null,
22
23
  subject: null,
23
24
  date_raw: null,
24
25
  date_iso: null,
@@ -77,10 +78,21 @@ async function processInline(text, depth, baseHistory = [], customDetectors = []
77
78
  ? { name: email.from.name, address: email.from.address }
78
79
  : (email.from ? { address: email.from } : null);
79
80
  fromNormalized = (0, utils_1.normalizeFrom)(fromNormalized);
81
+ // Normalize to address
82
+ let toNormalized = typeof email.to === 'object'
83
+ ? { name: email.to.name, address: email.to.address }
84
+ : (email.to ? { address: email.to } : null);
85
+ if (typeof email.to === 'string') {
86
+ toNormalized = (0, utils_1.normalizeFrom)({ address: email.to });
87
+ }
88
+ else if (toNormalized) {
89
+ toNormalized = (0, utils_1.normalizeFrom)(toNormalized);
90
+ }
80
91
  // Add this forward level to history
81
92
  const cleanedBody = (0, utils_1.cleanText)(email.body || '');
82
93
  history.push({
83
94
  from: fromNormalized,
95
+ to: toNormalized,
84
96
  subject: email.subject || null,
85
97
  date_raw: email.date || null,
86
98
  date_iso: dateIso,
@@ -105,10 +117,11 @@ async function processInline(text, depth, baseHistory = [], customDetectors = []
105
117
  date_raw: deepestEntry.date_raw,
106
118
  date_iso: deepestEntry.date_iso,
107
119
  text: deepestEntry.text,
120
+ to: deepestEntry.to,
108
121
  attachments: [],
109
122
  history: history.slice().reverse(),
110
123
  diagnostics: {
111
- method: (deepestEntry.flags.find(f => f.startsWith('method:')) || 'inline'),
124
+ method: (deepestEntry.flags.find(f => f.startsWith('method:'))?.replace('method:', '') || 'inline'),
112
125
  depth: currentDepth - startingDepth,
113
126
  parsedOk: true,
114
127
  warnings: warnings
@@ -123,6 +136,7 @@ async function processInline(text, depth, baseHistory = [], customDetectors = []
123
136
  date_raw: currentEntry.date_raw,
124
137
  date_iso: currentEntry.date_iso,
125
138
  text: currentEntry.text || (0, utils_1.cleanText)(currentText),
139
+ to: currentEntry.to,
126
140
  attachments: [],
127
141
  history: history.slice().reverse(),
128
142
  diagnostics: {
@@ -8,6 +8,7 @@ export interface MimeResult {
8
8
  history: HistoryEntry[];
9
9
  metadata?: {
10
10
  from?: any;
11
+ to?: any;
11
12
  subject?: string;
12
13
  date?: Date;
13
14
  };
@@ -23,6 +23,10 @@ async function processMime(raw, options) {
23
23
  name: parsed.from.value[0].name,
24
24
  address: parsed.from.value[0].address
25
25
  } : null,
26
+ to: parsed.to?.value?.[0] ? {
27
+ name: parsed.to.value[0].name,
28
+ address: parsed.to.value[0].address
29
+ } : null,
26
30
  subject: parsed.subject || null,
27
31
  date_raw: parsed.date?.toString() || null,
28
32
  date_iso: parsed.date ? parsed.date.toISOString() : null,
@@ -56,6 +60,7 @@ async function processMime(raw, options) {
56
60
  history,
57
61
  metadata: {
58
62
  from: parsed.from,
63
+ to: parsed.to,
59
64
  subject: parsed.subject,
60
65
  date: parsed.date
61
66
  }
package/dist/types.d.ts CHANGED
@@ -18,6 +18,7 @@ export interface Diagnostics {
18
18
  }
19
19
  export interface HistoryEntry {
20
20
  from: EmailAddress | null;
21
+ to: EmailAddress | null;
21
22
  subject: string | null;
22
23
  date_raw: string | null;
23
24
  date_iso: string | null;
@@ -28,6 +29,7 @@ export interface HistoryEntry {
28
29
  }
29
30
  export interface ResultObject {
30
31
  from: EmailAddress | null;
32
+ to: EmailAddress | null;
31
33
  subject: string | null;
32
34
  date_raw: string | null;
33
35
  date_iso: string | null;
package/dist/utils.js CHANGED
@@ -221,6 +221,9 @@ function normalizeFrom(from) {
221
221
  if (from.address) {
222
222
  from.address = from.address.replace(/^[\*\_]+|[\*\_]+$/g, '').trim();
223
223
  }
224
+ // FINAL VALIDATION: If at the end we have no address and no name, return null
225
+ if (!from.address && !from.name)
226
+ return null;
224
227
  return from;
225
228
  }
226
229
  function normalizeParserResult(parsed, method, depth, warnings = []) {
@@ -230,7 +233,6 @@ function normalizeParserResult(parsed, method, depth, warnings = []) {
230
233
  // Normalize From
231
234
  let from = null;
232
235
  if (email.from && typeof email.from === 'object') {
233
- // Only set from if we have at least an address
234
236
  if (email.from.address) {
235
237
  from = { name: email.from.name, address: email.from.address };
236
238
  }
@@ -238,6 +240,22 @@ function normalizeParserResult(parsed, method, depth, warnings = []) {
238
240
  else if (typeof email.from === 'string' && email.from.trim()) {
239
241
  from = { address: email.from.trim() };
240
242
  }
243
+ // Normalize To
244
+ let to_addr = null;
245
+ if (email.to && typeof email.to === 'object') {
246
+ if (Array.isArray(email.to)) {
247
+ if (email.to.length > 0) {
248
+ const first = email.to[0];
249
+ to_addr = typeof first === 'string' ? { address: first } : { name: first.name, address: first.address };
250
+ }
251
+ }
252
+ else {
253
+ to_addr = { name: email.to.name, address: email.to.address };
254
+ }
255
+ }
256
+ else if (typeof email.to === 'string' && email.to.trim()) {
257
+ to_addr = { address: email.to.trim() };
258
+ }
241
259
  const date_raw = email.date || null;
242
260
  const date_iso = normalizeDateToISO(date_raw);
243
261
  if (!date_iso && date_raw) {
@@ -245,6 +263,7 @@ function normalizeParserResult(parsed, method, depth, warnings = []) {
245
263
  }
246
264
  return {
247
265
  from,
266
+ to: to_addr,
248
267
  subject: email.subject || null,
249
268
  date_raw,
250
269
  date_iso,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "email-origin-chain",
3
- "version": "1.0.7",
3
+ "version": "1.0.10",
4
4
  "description": "Uncover the full audit trail of your email threads. Recursively reconstructs the entire conversation history with instant access to the original sender and true source message.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",