Initial Commit
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# cid-resolver
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Resolves Content-ID references inside HTML email bodies. Email clients
|
||||
that send inline images embed them as MIME parts with a
|
||||
`Content-ID` header and reference them in HTML with
|
||||
`<img src="cid:<id>">`. After parsing, the rendered HTML needs those
|
||||
references replaced by something a downstream consumer (an inbox UI,
|
||||
a digest builder, an audit trail) can serve — either a data URI, a
|
||||
static URL, or an explicit URL mapping.
|
||||
|
||||
## Features
|
||||
|
||||
- Three resolution strategies, evaluated in order: explicit
|
||||
`urlMap` (`Object.hasOwn`-checked so prototype keys cannot leak),
|
||||
`urlPrefix` + filename / contentHash, then data URI fallback.
|
||||
- Configurable `maxDataUriSize` (default 5 MiB) — attachments larger
|
||||
than the cap fall through to the unresolved list rather than
|
||||
exploding the HTML string.
|
||||
- Deduplicates CID references via `extractCidReferences`.
|
||||
- `isReferencedInline` short-circuits the lookup so callers can
|
||||
classify attachments as "inline" vs "true attachment" before
|
||||
serialising.
|
||||
- Strict input validation — non-string `html` inputs raise
|
||||
`EMAIL_PROCESSING.INVALID_INPUT`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { resolveCidReferences } from "@suaveplan/email-processing/cid-resolver";
|
||||
|
||||
const out = resolveCidReferences(parsed.html ?? "", processedAttachments);
|
||||
console.log(out.resolvedCount, out.unresolvedCids);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
resolveCidReferences,
|
||||
isReferencedInline,
|
||||
} from "@suaveplan/email-processing/cid-resolver";
|
||||
|
||||
const result = resolveCidReferences(parsed.html ?? "", processed, {
|
||||
urlMap: { "logo@brand": "https://cdn.brand/logo.png" },
|
||||
urlPrefix: "/inbox/inline/",
|
||||
maxDataUriSize: 2 * 1024 * 1024,
|
||||
});
|
||||
|
||||
const inlineAttachments = processed.filter((a) =>
|
||||
isReferencedInline(parsed.html ?? "", a)
|
||||
);
|
||||
const trueAttachments = processed.filter(
|
||||
(a) => !isReferencedInline(parsed.html ?? "", a)
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `CidResolverOptions` — `{ maxDataUriSize?, urlPrefix?, urlMap? }`.
|
||||
- `CidResolutionResult` — `{ html, totalReferences, resolvedCount, unresolvedCids }`.
|
||||
- `resolveCidReferences(html, attachments, options?)` — primary entry.
|
||||
- `extractCidReferences(html)` — deduped list of CIDs found.
|
||||
- `isReferencedInline(html, attachment)` — boolean.
|
||||
|
||||
Errors: `INVALID_INPUT` for non-string `html`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`Object.hasOwn(urlMap, cleanRef)` is used in preference to a direct
|
||||
truthy check so a deliberate `urlMap: { id: "" }` mapping is still
|
||||
recognised as "set", and so a polluted prototype cannot inject phantom
|
||||
keys. `String.prototype.matchAll` is used over `regex.exec` loops to
|
||||
keep the resolver simple and avoid stateful `lastIndex` bugs. The data
|
||||
URI fallback uses `btoa(String.fromCharCode(...))` so it works in the
|
||||
browser and Node without buffer polyfills.
|
||||
Reference in New Issue
Block a user