@lunora/mail 1.0.0-alpha.13 → 1.0.0-alpha.14
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/dist/inbound/index.d.mts +15 -4
- package/dist/inbound/index.d.ts +15 -4
- package/dist/inbound/index.mjs +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/{createCaptureSink-Di-5AJUs.mjs → createCaptureSink-B0c7fQJf.mjs} +24 -12
- package/dist/packem_shared/{createInboundEmailHandler-BW_s0-Vc.mjs → createInboundEmailHandler-BMB5t-qh.mjs} +10 -20
- package/dist/packem_shared/{parseInboundEmail-6lafQBT1.mjs → parseInboundEmail-DXVOH0wz.mjs} +2 -1
- package/dist/packem_shared/shard-DEDcQoFg.mjs +36 -0
- package/dist/packem_shared/{shard.d-CL2Lmliv.d.mts → shard.d-C4kxvfmd.d.mts} +3 -8
- package/dist/packem_shared/{shard.d-CL2Lmliv.d.ts → shard.d-C4kxvfmd.d.ts} +3 -8
- package/dist/testing.mjs +3 -1
- package/package.json +2 -2
- package/dist/packem_shared/shard-CJ-TvmfT.mjs +0 -13
package/dist/inbound/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DurableObjectJurisdiction, S as ShardNamespaceLike } from "../packem_shared/shard.d-
|
|
2
|
-
export type { a as ShardStubLike } from "../packem_shared/shard.d-
|
|
1
|
+
import { D as DurableObjectJurisdiction, S as ShardNamespaceLike } from "../packem_shared/shard.d-C4kxvfmd.mjs";
|
|
2
|
+
export type { a as ShardStubLike } from "../packem_shared/shard.d-C4kxvfmd.mjs";
|
|
3
3
|
/** A raw RFC 822 message as accepted by the parser. */
|
|
4
4
|
type RawInboundEmail = ArrayBuffer | ReadableStream<Uint8Array> | string | Uint8Array;
|
|
5
5
|
/** One parsed attachment. `content` is preserved as the parser decoded it. */
|
|
@@ -25,6 +25,14 @@ interface InboundAttachment {
|
|
|
25
25
|
* on `email.from` alone — gate on these verdicts (or your own policy) instead.
|
|
26
26
|
* Verdicts are best-effort: when the receiving MX did not stamp an
|
|
27
27
|
* `Authentication-Results` header, every field is `null` ("unknown").
|
|
28
|
+
*
|
|
29
|
+
* SECURITY: verdicts are read from the **first/topmost** `Authentication-Results`
|
|
30
|
+
* header in document order. The receiving MX prepends its own genuine header per
|
|
31
|
+
* RFC 8601, so the topmost occurrence is the trustworthy one; any lower
|
|
32
|
+
* occurrences (which an untrusted sender can inject into the raw message) are
|
|
33
|
+
* ignored. As defense-in-depth a consumer may additionally verify the topmost
|
|
34
|
+
* header's `authserv-id` matches its receiving MX (e.g. Cloudflare) — that needs
|
|
35
|
+
* config this runtime-agnostic parser does not carry, so it is left to the host.
|
|
28
36
|
*/
|
|
29
37
|
interface InboundAuthentication {
|
|
30
38
|
/** DKIM verdict (`"pass"`/`"fail"`/…), or `null` when not reported. */
|
|
@@ -40,8 +48,11 @@ interface InboundEmail {
|
|
|
40
48
|
attachments: InboundAttachment[];
|
|
41
49
|
/**
|
|
42
50
|
* Sender-authentication verdicts (DKIM/SPF/DMARC) parsed from the receiving
|
|
43
|
-
* MX's `Authentication-Results` header. SECURITY: see
|
|
44
|
-
* — `from` is spoofable; gate trust on these
|
|
51
|
+
* MX's **first/topmost** `Authentication-Results` header. SECURITY: see
|
|
52
|
+
* {@link InboundAuthentication} — `from` is spoofable; gate trust on these
|
|
53
|
+
* verdicts, not on `from`. Reading the raw `headers["authentication-results"]`
|
|
54
|
+
* map instead exposes last-wins (a lower, potentially attacker-injected)
|
|
55
|
+
* value — trust `authentication`, not the raw map.
|
|
45
56
|
*/
|
|
46
57
|
authentication: InboundAuthentication;
|
|
47
58
|
/** Sender mailbox (`from`), CR/LF-checked. Empty string when the message omitted it. SECURITY: spoofable — do not trust for authorization. */
|
package/dist/inbound/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DurableObjectJurisdiction, S as ShardNamespaceLike } from "../packem_shared/shard.d-
|
|
2
|
-
export type { a as ShardStubLike } from "../packem_shared/shard.d-
|
|
1
|
+
import { D as DurableObjectJurisdiction, S as ShardNamespaceLike } from "../packem_shared/shard.d-C4kxvfmd.js";
|
|
2
|
+
export type { a as ShardStubLike } from "../packem_shared/shard.d-C4kxvfmd.js";
|
|
3
3
|
/** A raw RFC 822 message as accepted by the parser. */
|
|
4
4
|
type RawInboundEmail = ArrayBuffer | ReadableStream<Uint8Array> | string | Uint8Array;
|
|
5
5
|
/** One parsed attachment. `content` is preserved as the parser decoded it. */
|
|
@@ -25,6 +25,14 @@ interface InboundAttachment {
|
|
|
25
25
|
* on `email.from` alone — gate on these verdicts (or your own policy) instead.
|
|
26
26
|
* Verdicts are best-effort: when the receiving MX did not stamp an
|
|
27
27
|
* `Authentication-Results` header, every field is `null` ("unknown").
|
|
28
|
+
*
|
|
29
|
+
* SECURITY: verdicts are read from the **first/topmost** `Authentication-Results`
|
|
30
|
+
* header in document order. The receiving MX prepends its own genuine header per
|
|
31
|
+
* RFC 8601, so the topmost occurrence is the trustworthy one; any lower
|
|
32
|
+
* occurrences (which an untrusted sender can inject into the raw message) are
|
|
33
|
+
* ignored. As defense-in-depth a consumer may additionally verify the topmost
|
|
34
|
+
* header's `authserv-id` matches its receiving MX (e.g. Cloudflare) — that needs
|
|
35
|
+
* config this runtime-agnostic parser does not carry, so it is left to the host.
|
|
28
36
|
*/
|
|
29
37
|
interface InboundAuthentication {
|
|
30
38
|
/** DKIM verdict (`"pass"`/`"fail"`/…), or `null` when not reported. */
|
|
@@ -40,8 +48,11 @@ interface InboundEmail {
|
|
|
40
48
|
attachments: InboundAttachment[];
|
|
41
49
|
/**
|
|
42
50
|
* Sender-authentication verdicts (DKIM/SPF/DMARC) parsed from the receiving
|
|
43
|
-
* MX's `Authentication-Results` header. SECURITY: see
|
|
44
|
-
* — `from` is spoofable; gate trust on these
|
|
51
|
+
* MX's **first/topmost** `Authentication-Results` header. SECURITY: see
|
|
52
|
+
* {@link InboundAuthentication} — `from` is spoofable; gate trust on these
|
|
53
|
+
* verdicts, not on `from`. Reading the raw `headers["authentication-results"]`
|
|
54
|
+
* map instead exposes last-wins (a lower, potentially attacker-injected)
|
|
55
|
+
* value — trust `authentication`, not the raw map.
|
|
45
56
|
*/
|
|
46
57
|
authentication: InboundAuthentication;
|
|
47
58
|
/** Sender mailbox (`from`), CR/LF-checked. Empty string when the message omitted it. SECURITY: spoofable — do not trust for authorization. */
|
package/dist/inbound/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createInboundEmailHandler, dispatchToLunoraFunction } from '../packem_shared/createInboundEmailHandler-
|
|
2
|
-
export { parseInboundEmail } from '../packem_shared/parseInboundEmail-
|
|
1
|
+
export { createInboundEmailHandler, dispatchToLunoraFunction } from '../packem_shared/createInboundEmailHandler-BMB5t-qh.mjs';
|
|
2
|
+
export { parseInboundEmail } from '../packem_shared/parseInboundEmail-DXVOH0wz.mjs';
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { M as MailTransport, L as LunoraMailOptions, a as Mailer, b as MailboxSink, S as SendOptions } from "./packem_shared/capture-transport.d-ChnhdPO2.mjs";
|
|
2
2
|
export { type C as CapturedMail, type Q as QueueLike, type c as SendPayload, d as createCaptureTransport } from "./packem_shared/capture-transport.d-ChnhdPO2.mjs";
|
|
3
|
-
import { D as DurableObjectJurisdiction } from "./packem_shared/shard.d-
|
|
3
|
+
import { D as DurableObjectJurisdiction } from "./packem_shared/shard.d-C4kxvfmd.mjs";
|
|
4
4
|
import { ReactElement } from 'react';
|
|
5
5
|
/**
|
|
6
6
|
* Sends a raw RFC 822 message through a Worker's Email send binding. The Workers
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { M as MailTransport, L as LunoraMailOptions, a as Mailer, b as MailboxSink, S as SendOptions } from "./packem_shared/capture-transport.d-ChnhdPO2.js";
|
|
2
2
|
export { type C as CapturedMail, type Q as QueueLike, type c as SendPayload, d as createCaptureTransport } from "./packem_shared/capture-transport.d-ChnhdPO2.js";
|
|
3
|
-
import { D as DurableObjectJurisdiction } from "./packem_shared/shard.d-
|
|
3
|
+
import { D as DurableObjectJurisdiction } from "./packem_shared/shard.d-C4kxvfmd.js";
|
|
4
4
|
import { ReactElement } from 'react';
|
|
5
5
|
/**
|
|
6
6
|
* Sends a raw RFC 822 message through a Worker's Email send binding. The Workers
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { createCaptureTransport } from './packem_shared/createCaptureTransport-Crz_8822.mjs';
|
|
2
2
|
export { createCloudflareTransport } from './packem_shared/createCloudflareTransport-DXh1nUNi.mjs';
|
|
3
3
|
export { default as createMailer } from './packem_shared/createMailer-B4Z_Cgiu.mjs';
|
|
4
|
-
export { createCaptureSink, createMailerFromEnv, shouldCaptureMail } from './packem_shared/createCaptureSink-
|
|
4
|
+
export { createCaptureSink, createMailerFromEnv, shouldCaptureMail } from './packem_shared/createCaptureSink-B0c7fQJf.mjs';
|
|
5
5
|
export { consumeQueuedSend, toQueuedPayload } from './packem_shared/consumeQueuedSend-B9hTDOZ6.mjs';
|
|
6
6
|
export { default as renderEmail } from './packem_shared/renderEmail-hyS1bpVP.mjs';
|
|
7
7
|
export { default as createResendTransport } from './packem_shared/createResendTransport-CMQWjZoi.mjs';
|
package/dist/packem_shared/{createCaptureSink-Di-5AJUs.mjs → createCaptureSink-B0c7fQJf.mjs}
RENAMED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
2
|
import { createCaptureTransport } from './createCaptureTransport-Crz_8822.mjs';
|
|
3
3
|
import createMailer from './createMailer-B4Z_Cgiu.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import { D as DEFAULT_ROOT_SHARD, p as postShardRpc } from './shard-DEDcQoFg.mjs';
|
|
5
5
|
|
|
6
6
|
const RECORD_MAIL_OP = "__lunora_admin__:recordMail";
|
|
7
|
-
const DEFAULT_ROOT_SHARD = "__root__";
|
|
8
7
|
const DEV_ENVIRONMENT_PATTERN = /^(?:dev(?:elopment)?|local(?:host)?|test)$/iu;
|
|
9
8
|
const ENVIRONMENT_VARS = ["CF_ENV", "ENVIRONMENT", "NODE_ENV", "WORKER_ENV"];
|
|
10
9
|
const requireStringEnv = (env, name) => {
|
|
@@ -17,7 +16,16 @@ const requireStringEnv = (env, name) => {
|
|
|
17
16
|
const shouldCaptureMail = (env) => {
|
|
18
17
|
const flag = env["LUNORA_MAIL_CAPTURE"];
|
|
19
18
|
if (typeof flag === "string") {
|
|
20
|
-
|
|
19
|
+
const normalized = flag.toLowerCase();
|
|
20
|
+
if (normalized === "1" || normalized === "true") {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (normalized === "0" || normalized === "false") {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
console.warn(
|
|
27
|
+
`@lunora/mail: unrecognized LUNORA_MAIL_CAPTURE value "${flag}" — expected "1"/"true" or "0"/"false"; falling back to environment detection.`
|
|
28
|
+
);
|
|
21
29
|
}
|
|
22
30
|
return ENVIRONMENT_VARS.some((key) => {
|
|
23
31
|
const value = env[key];
|
|
@@ -32,15 +40,19 @@ const createCaptureSink = (env, rootShard = DEFAULT_ROOT_SHARD, jurisdiction) =>
|
|
|
32
40
|
if (binding === void 0 || adminToken === void 0) {
|
|
33
41
|
return { id: "uncaptured" };
|
|
34
42
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
try {
|
|
44
|
+
const body = await postShardRpc(binding, {
|
|
45
|
+
adminToken,
|
|
46
|
+
envelope: { args: mail, functionPath: RECORD_MAIL_OP },
|
|
47
|
+
jurisdiction,
|
|
48
|
+
label: "@lunora/mail: recording captured mail",
|
|
49
|
+
shardKey: rootShard
|
|
50
|
+
});
|
|
51
|
+
return { id: body.result?.id ?? "captured" };
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error("@lunora/mail: failed to record captured mail into the studio inbox —", error);
|
|
54
|
+
return { id: "uncaptured" };
|
|
55
|
+
}
|
|
44
56
|
}
|
|
45
57
|
};
|
|
46
58
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
import {
|
|
2
|
+
import { p as postShardRpc, D as DEFAULT_ROOT_SHARD } from './shard-DEDcQoFg.mjs';
|
|
3
3
|
|
|
4
4
|
const GENERIC_REJECT_REASON = "message could not be processed";
|
|
5
5
|
const rejectOnError = (error, context) => {
|
|
@@ -24,11 +24,11 @@ const createInboundEmailHandler = (options) => {
|
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
};
|
|
27
|
-
const
|
|
27
|
+
const BASE64_CHUNK = 32768;
|
|
28
28
|
const toBase64 = (bytes) => {
|
|
29
29
|
let binary = "";
|
|
30
|
-
for (
|
|
31
|
-
binary += String.
|
|
30
|
+
for (let index = 0; index < bytes.length; index += BASE64_CHUNK) {
|
|
31
|
+
binary += String.fromCharCode(...bytes.subarray(index, index + BASE64_CHUNK));
|
|
32
32
|
}
|
|
33
33
|
return btoa(binary);
|
|
34
34
|
};
|
|
@@ -61,23 +61,13 @@ const dispatchToLunoraFunction = (options) => {
|
|
|
61
61
|
functionPath: options.functionPath,
|
|
62
62
|
shardKey
|
|
63
63
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
await postShardRpc(options.shard, {
|
|
65
|
+
adminToken,
|
|
66
|
+
envelope,
|
|
67
|
+
jurisdiction: options.jurisdiction,
|
|
68
|
+
label: `@lunora/mail/inbound: dispatch to \`${options.functionPath}\``,
|
|
69
|
+
shardKey
|
|
70
70
|
});
|
|
71
|
-
if (response.ok === false) {
|
|
72
|
-
throw new LunoraError("INTERNAL", `@lunora/mail/inbound: dispatch to \`${options.functionPath}\` failed (HTTP ${String(response.status ?? "?")}).`);
|
|
73
|
-
}
|
|
74
|
-
const body = await response.json();
|
|
75
|
-
if (typeof body === "object" && body !== null && "error" in body) {
|
|
76
|
-
const { error } = body;
|
|
77
|
-
if (error !== void 0 && error !== null) {
|
|
78
|
-
throw new LunoraError("INTERNAL", `@lunora/mail/inbound: dispatch to \`${options.functionPath}\` returned an error: ${JSON.stringify(error)}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
71
|
};
|
|
82
72
|
};
|
|
83
73
|
|
package/dist/packem_shared/{parseInboundEmail-6lafQBT1.mjs → parseInboundEmail-DXVOH0wz.mjs}
RENAMED
|
@@ -38,6 +38,7 @@ const parseInboundEmail = async (raw) => {
|
|
|
38
38
|
assertSafeHeaderValue(`inbound header \`${header.key}\``, header.value);
|
|
39
39
|
headers[header.key] = header.value;
|
|
40
40
|
}
|
|
41
|
+
const topmostAuthResults = parsed.headers.find((header) => header.key === "authentication-results")?.value;
|
|
41
42
|
const to = (parsed.to ?? []).map((entry) => {
|
|
42
43
|
const formatted = formatAddress(entry);
|
|
43
44
|
assertSafeHeaderValue("inbound to", formatted);
|
|
@@ -56,7 +57,7 @@ const parseInboundEmail = async (raw) => {
|
|
|
56
57
|
});
|
|
57
58
|
return {
|
|
58
59
|
attachments,
|
|
59
|
-
authentication: parseAuthentication(
|
|
60
|
+
authentication: parseAuthentication(topmostAuthResults),
|
|
60
61
|
from,
|
|
61
62
|
headers,
|
|
62
63
|
...parsed.html === void 0 ? {} : { html: parsed.html },
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_ROOT_SHARD = "__root__";
|
|
4
|
+
const applyJurisdiction = (namespace, jurisdiction) => {
|
|
5
|
+
if (jurisdiction === void 0) {
|
|
6
|
+
return namespace;
|
|
7
|
+
}
|
|
8
|
+
if (typeof namespace.jurisdiction !== "function") {
|
|
9
|
+
throw new TypeError(
|
|
10
|
+
`@lunora/mail: Durable Object namespace does not support jurisdiction("${jurisdiction}") — update @cloudflare/workers-types or remove the jurisdiction option`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
return namespace.jurisdiction(jurisdiction);
|
|
14
|
+
};
|
|
15
|
+
const postShardRpc = async (namespace, options) => {
|
|
16
|
+
const scoped = applyJurisdiction(namespace, options.jurisdiction);
|
|
17
|
+
const stub = scoped.get(scoped.idFromName(options.shardKey));
|
|
18
|
+
const response = await stub.fetch("https://shard.internal/rpc", {
|
|
19
|
+
body: JSON.stringify(options.envelope),
|
|
20
|
+
headers: { authorization: `Bearer ${options.adminToken}`, "content-type": "application/json" },
|
|
21
|
+
method: "POST"
|
|
22
|
+
});
|
|
23
|
+
if (response.ok === false) {
|
|
24
|
+
throw new LunoraError("INTERNAL", `${options.label} failed (HTTP ${String(response.status ?? "?")}).`);
|
|
25
|
+
}
|
|
26
|
+
const body = await response.json();
|
|
27
|
+
if (typeof body === "object" && body !== null && "error" in body) {
|
|
28
|
+
const { error } = body;
|
|
29
|
+
if (error !== void 0 && error !== null) {
|
|
30
|
+
throw new LunoraError("INTERNAL", `${options.label} returned an error: ${JSON.stringify(error)}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return body;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export { DEFAULT_ROOT_SHARD as D, postShardRpc as p };
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structural projections of the `SHARD` Durable Object namespace + one shard
|
|
3
|
-
* stub, shared by the inbound dispatcher. Mirrors the shapes the outbound dev
|
|
4
|
-
* capture sink uses (`packages/mail/src/from-env.ts`) so inbound dispatch routes
|
|
5
|
-
* a parsed message into a Lunora function over the exact same admin-RPC-over-shard
|
|
6
|
-
* path — without importing any Cloudflare types into `@lunora/mail`.
|
|
7
|
-
*/
|
|
8
|
-
/** Structural projection of one shard stub — only `fetch` returning something with `.json()`. */
|
|
1
|
+
/** Structural projection of one shard stub — only `fetch` returning a Fetch-`Response`-like object. */
|
|
9
2
|
interface ShardStubLike {
|
|
10
3
|
fetch: (input: string, init?: {
|
|
11
4
|
body?: string;
|
|
@@ -13,6 +6,8 @@ interface ShardStubLike {
|
|
|
13
6
|
method?: string;
|
|
14
7
|
}) => Promise<{
|
|
15
8
|
json: () => Promise<unknown>;
|
|
9
|
+
ok?: boolean;
|
|
10
|
+
status?: number;
|
|
16
11
|
}>;
|
|
17
12
|
}
|
|
18
13
|
/**
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structural projections of the `SHARD` Durable Object namespace + one shard
|
|
3
|
-
* stub, shared by the inbound dispatcher. Mirrors the shapes the outbound dev
|
|
4
|
-
* capture sink uses (`packages/mail/src/from-env.ts`) so inbound dispatch routes
|
|
5
|
-
* a parsed message into a Lunora function over the exact same admin-RPC-over-shard
|
|
6
|
-
* path — without importing any Cloudflare types into `@lunora/mail`.
|
|
7
|
-
*/
|
|
8
|
-
/** Structural projection of one shard stub — only `fetch` returning something with `.json()`. */
|
|
1
|
+
/** Structural projection of one shard stub — only `fetch` returning a Fetch-`Response`-like object. */
|
|
9
2
|
interface ShardStubLike {
|
|
10
3
|
fetch: (input: string, init?: {
|
|
11
4
|
body?: string;
|
|
@@ -13,6 +6,8 @@ interface ShardStubLike {
|
|
|
13
6
|
method?: string;
|
|
14
7
|
}) => Promise<{
|
|
15
8
|
json: () => Promise<unknown>;
|
|
9
|
+
ok?: boolean;
|
|
10
|
+
status?: number;
|
|
16
11
|
}>;
|
|
17
12
|
}
|
|
18
13
|
/**
|
package/dist/testing.mjs
CHANGED
|
@@ -43,6 +43,8 @@ const waitForMail = async (options) => {
|
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
45
|
const URL_PATTERN = /https?:\/\/[^\s"'<>)]+/g;
|
|
46
|
+
const AMPERSAND_ENTITY = /&(?:amp|#0*38|#x0*26);/giu;
|
|
47
|
+
const decodeUrlEntities = (url) => url.replaceAll(AMPERSAND_ENTITY, "&");
|
|
46
48
|
const extractLink = (mail, options = {}) => {
|
|
47
49
|
for (const source of [mail.html, mail.text]) {
|
|
48
50
|
if (source === void 0) {
|
|
@@ -51,7 +53,7 @@ const extractLink = (mail, options = {}) => {
|
|
|
51
53
|
const matches = source.match(URL_PATTERN) ?? [];
|
|
52
54
|
const link = matches.find((candidate) => options.match === void 0 || candidate.includes(options.match));
|
|
53
55
|
if (link !== void 0) {
|
|
54
|
-
return link;
|
|
56
|
+
return decodeUrlEntities(link);
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
throw new LunoraError(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/mail",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.14",
|
|
4
4
|
"description": "Email for Lunora: Resend adapter, TSX templates, and queue-backed sends",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"access": "public"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
57
|
+
"@lunora/errors": "1.0.0-alpha.4",
|
|
58
58
|
"@react-email/render": "2.0.10",
|
|
59
59
|
"@visulima/email": "1.0.0",
|
|
60
60
|
"postal-mime": "2.7.5"
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const applyJurisdiction = (namespace, jurisdiction) => {
|
|
2
|
-
if (jurisdiction === void 0) {
|
|
3
|
-
return namespace;
|
|
4
|
-
}
|
|
5
|
-
if (typeof namespace.jurisdiction !== "function") {
|
|
6
|
-
throw new TypeError(
|
|
7
|
-
`@lunora/mail: Durable Object namespace does not support jurisdiction("${jurisdiction}") — update @cloudflare/workers-types or remove the jurisdiction option`
|
|
8
|
-
);
|
|
9
|
-
}
|
|
10
|
-
return namespace.jurisdiction(jurisdiction);
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export { applyJurisdiction as a };
|