@gravito/signal 1.0.0-beta.1 → 1.0.1
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/CHANGELOG.md +16 -0
- package/dist/ReactRenderer-CMCAOEPH.mjs +28 -0
- package/dist/VueRenderer-DWTCD2RF.mjs +31 -0
- package/dist/index.d.mts +24 -26
- package/dist/index.d.ts +24 -26
- package/dist/index.js +499 -501
- package/dist/index.mjs +493 -15
- package/package.json +17 -16
- package/src/Mailable.ts +44 -10
- package/src/OrbitSignal.ts +28 -64
- package/src/augmentation.ts +1 -1
- package/src/dev/DevServer.ts +135 -73
- package/src/dev/ui/preview.ts +35 -0
- package/src/dev/ui/shared.ts +1 -0
- package/src/renderers/ReactRenderer.ts +8 -3
- package/src/renderers/VueRenderer.ts +10 -3
- package/src/types.ts +11 -0
- package/tests/dev-server.test.ts +66 -0
- package/tests/log-transport.test.ts +21 -0
- package/tests/mailable-extra.test.ts +61 -0
- package/tests/mailable.test.ts +2 -2
- package/tests/orbit-signal.test.ts +43 -0
- package/tests/renderers.test.ts +14 -12
- package/tests/template-renderer.test.ts +24 -0
- package/tests/ui.test.ts +37 -0
- package/tsconfig.json +11 -4
package/dist/index.js
CHANGED
|
@@ -33,59 +33,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
));
|
|
34
34
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
35
35
|
|
|
36
|
-
// src/dev/DevMailbox.ts
|
|
37
|
-
var import_node_crypto, DevMailbox;
|
|
38
|
-
var init_DevMailbox = __esm({
|
|
39
|
-
"src/dev/DevMailbox.ts"() {
|
|
40
|
-
"use strict";
|
|
41
|
-
import_node_crypto = require("crypto");
|
|
42
|
-
DevMailbox = class {
|
|
43
|
-
entries = [];
|
|
44
|
-
maxEntries = 50;
|
|
45
|
-
add(message) {
|
|
46
|
-
const entry = {
|
|
47
|
-
id: (0, import_node_crypto.randomUUID)(),
|
|
48
|
-
envelope: {
|
|
49
|
-
from: message.from,
|
|
50
|
-
to: message.to,
|
|
51
|
-
...message.cc ? { cc: message.cc } : {},
|
|
52
|
-
...message.bcc ? { bcc: message.bcc } : {},
|
|
53
|
-
...message.replyTo ? { replyTo: message.replyTo } : {},
|
|
54
|
-
subject: message.subject,
|
|
55
|
-
...message.priority ? { priority: message.priority } : {},
|
|
56
|
-
...message.attachments ? { attachments: message.attachments } : {}
|
|
57
|
-
},
|
|
58
|
-
html: message.html,
|
|
59
|
-
...message.text ? { text: message.text } : {},
|
|
60
|
-
sentAt: /* @__PURE__ */ new Date()
|
|
61
|
-
};
|
|
62
|
-
this.entries.unshift(entry);
|
|
63
|
-
if (this.entries.length > this.maxEntries) {
|
|
64
|
-
this.entries = this.entries.slice(0, this.maxEntries);
|
|
65
|
-
}
|
|
66
|
-
return entry;
|
|
67
|
-
}
|
|
68
|
-
list() {
|
|
69
|
-
return this.entries;
|
|
70
|
-
}
|
|
71
|
-
get(id) {
|
|
72
|
-
return this.entries.find((e) => e.id === id);
|
|
73
|
-
}
|
|
74
|
-
delete(id) {
|
|
75
|
-
const index = this.entries.findIndex((e) => e.id === id);
|
|
76
|
-
if (index !== -1) {
|
|
77
|
-
this.entries.splice(index, 1);
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
clear() {
|
|
83
|
-
this.entries = [];
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
36
|
// src/renderers/ReactRenderer.ts
|
|
90
37
|
var ReactRenderer_exports = {};
|
|
91
38
|
__export(ReactRenderer_exports, {
|
|
@@ -96,13 +43,14 @@ var init_ReactRenderer = __esm({
|
|
|
96
43
|
"src/renderers/ReactRenderer.ts"() {
|
|
97
44
|
"use strict";
|
|
98
45
|
ReactRenderer = class {
|
|
99
|
-
constructor(component, props) {
|
|
46
|
+
constructor(component, props, deps = {}) {
|
|
100
47
|
this.component = component;
|
|
101
48
|
this.props = props;
|
|
49
|
+
this.deps = deps;
|
|
102
50
|
}
|
|
103
51
|
async render(data) {
|
|
104
|
-
const
|
|
105
|
-
const
|
|
52
|
+
const createElement = this.deps.createElement ?? (await import("react")).createElement;
|
|
53
|
+
const renderToStaticMarkup = this.deps.renderToStaticMarkup ?? (await import("react-dom/server")).renderToStaticMarkup;
|
|
106
54
|
const mergedProps = { ...this.props, ...data };
|
|
107
55
|
const element = createElement(this.component, mergedProps);
|
|
108
56
|
const html = renderToStaticMarkup(element);
|
|
@@ -37353,13 +37301,15 @@ var init_VueRenderer = __esm({
|
|
|
37353
37301
|
"src/renderers/VueRenderer.ts"() {
|
|
37354
37302
|
"use strict";
|
|
37355
37303
|
VueRenderer = class {
|
|
37356
|
-
constructor(component, props) {
|
|
37304
|
+
constructor(component, props, deps = {}) {
|
|
37357
37305
|
this.component = component;
|
|
37358
37306
|
this.props = props;
|
|
37307
|
+
this.deps = deps;
|
|
37359
37308
|
}
|
|
37360
37309
|
async render(data) {
|
|
37361
|
-
const
|
|
37362
|
-
const
|
|
37310
|
+
const createSSRApp = this.deps.createSSRApp ?? (await import("vue")).createSSRApp;
|
|
37311
|
+
const h = this.deps.h ?? (await import("vue")).h;
|
|
37312
|
+
const renderToString = this.deps.renderToString ?? (await Promise.resolve().then(() => __toESM(require_server_renderer()))).renderToString;
|
|
37363
37313
|
const mergedProps = { ...this.props, ...data };
|
|
37364
37314
|
const app = createSSRApp({
|
|
37365
37315
|
render: () => h(this.component, mergedProps)
|
|
@@ -37378,436 +37328,6 @@ var init_VueRenderer = __esm({
|
|
|
37378
37328
|
}
|
|
37379
37329
|
});
|
|
37380
37330
|
|
|
37381
|
-
// src/dev/ui/shared.ts
|
|
37382
|
-
var styles, layout;
|
|
37383
|
-
var init_shared = __esm({
|
|
37384
|
-
"src/dev/ui/shared.ts"() {
|
|
37385
|
-
"use strict";
|
|
37386
|
-
styles = `
|
|
37387
|
-
:root {
|
|
37388
|
-
--primary: #6366f1;
|
|
37389
|
-
--primary-dark: #4f46e5;
|
|
37390
|
-
--bg-dark: #0f172a;
|
|
37391
|
-
--bg-card: #1e293b;
|
|
37392
|
-
--text: #f1f5f9;
|
|
37393
|
-
--text-muted: #94a3b8;
|
|
37394
|
-
--border: #334155;
|
|
37395
|
-
--danger: #ef4444;
|
|
37396
|
-
}
|
|
37397
|
-
body { background: var(--bg-dark); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 20px; }
|
|
37398
|
-
.container { max-width: 1000px; margin: 0 auto; }
|
|
37399
|
-
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
|
37400
|
-
.title { font-size: 24px; font-weight: bold; display: flex; align-items: center; gap: 10px; }
|
|
37401
|
-
.card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
|
|
37402
|
-
.btn { background: var(--border); color: var(--text); border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; text-decoration: none; font-size: 14px; transition: 0.2s; }
|
|
37403
|
-
.btn:hover { background: var(--bg-card-hover); }
|
|
37404
|
-
.btn-primary { background: var(--primary); color: white; }
|
|
37405
|
-
.btn-primary:hover { background: var(--primary-dark); }
|
|
37406
|
-
.btn-danger { background: var(--danger); color: white; }
|
|
37407
|
-
.list-item { padding: 16px; border-bottom: 1px solid var(--border); display: flex; flex-direction: column; gap: 4px; text-decoration: none; color: inherit; transition: background 0.2s; }
|
|
37408
|
-
.list-item:last-child { border-bottom: none; }
|
|
37409
|
-
.list-item:hover { background: #334155; }
|
|
37410
|
-
.meta { display: flex; justify-content: space-between; font-size: 14px; color: var(--text-muted); }
|
|
37411
|
-
.subject { font-weight: 600; font-size: 16px; }
|
|
37412
|
-
.from { color: #cbd5e1; }
|
|
37413
|
-
.badge { background: #475569; padding: 2px 6px; border-radius: 4px; font-size: 12px; }
|
|
37414
|
-
.badge-high { background: #dc2626; color: white; }
|
|
37415
|
-
.empty { padding: 40px; text-align: center; color: var(--text-muted); }
|
|
37416
|
-
`;
|
|
37417
|
-
layout = (title, content) => `
|
|
37418
|
-
<!DOCTYPE html>
|
|
37419
|
-
<html>
|
|
37420
|
-
<head>
|
|
37421
|
-
<title>${title} - Gravito Mailbox</title>
|
|
37422
|
-
<style>${styles}</style>
|
|
37423
|
-
</head>
|
|
37424
|
-
<body>
|
|
37425
|
-
<div class="container">
|
|
37426
|
-
${content}
|
|
37427
|
-
</div>
|
|
37428
|
-
</body>
|
|
37429
|
-
</html>
|
|
37430
|
-
`;
|
|
37431
|
-
}
|
|
37432
|
-
});
|
|
37433
|
-
|
|
37434
|
-
// src/dev/ui/mailbox.ts
|
|
37435
|
-
function formatAddress(addr) {
|
|
37436
|
-
return addr.name ? `${addr.name} <${addr.address}>` : addr.address;
|
|
37437
|
-
}
|
|
37438
|
-
function timeAgo(date) {
|
|
37439
|
-
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
37440
|
-
if (seconds < 60) {
|
|
37441
|
-
return "Just now";
|
|
37442
|
-
}
|
|
37443
|
-
const minutes = Math.floor(seconds / 60);
|
|
37444
|
-
if (minutes < 60) {
|
|
37445
|
-
return `${minutes}m ago`;
|
|
37446
|
-
}
|
|
37447
|
-
const hours = Math.floor(minutes / 60);
|
|
37448
|
-
if (hours < 24) {
|
|
37449
|
-
return `${hours}h ago`;
|
|
37450
|
-
}
|
|
37451
|
-
return date.toLocaleDateString();
|
|
37452
|
-
}
|
|
37453
|
-
function getMailboxHtml(entries, prefix) {
|
|
37454
|
-
const list = entries.length === 0 ? '<div class="empty">No emails found in mailbox.</div>' : entries.map(
|
|
37455
|
-
(entry) => `
|
|
37456
|
-
<a href="${prefix}/${entry.id}" class="list-item">
|
|
37457
|
-
<div class="meta">
|
|
37458
|
-
<span class="from">${formatAddress(entry.envelope.from || { address: "Unknown" })}</span>
|
|
37459
|
-
<span>${timeAgo(entry.sentAt)}</span>
|
|
37460
|
-
</div>
|
|
37461
|
-
<div class="subject">
|
|
37462
|
-
${entry.envelope.priority === "high" ? '<span class="badge badge-high">High</span> ' : ""}
|
|
37463
|
-
${entry.envelope.subject || "(No Subject)"}
|
|
37464
|
-
</div>
|
|
37465
|
-
<div class="meta" style="margin-top: 4px;">
|
|
37466
|
-
To: ${entry.envelope.to?.map((t) => t.address).join(", ")}
|
|
37467
|
-
</div>
|
|
37468
|
-
</a>
|
|
37469
|
-
`
|
|
37470
|
-
).join("");
|
|
37471
|
-
const content = `
|
|
37472
|
-
<div class="header">
|
|
37473
|
-
<div class="title">\u{1F4EC} Gravito Mailbox</div>
|
|
37474
|
-
${entries.length > 0 ? `<button onclick="clearAll()" class="btn btn-danger">Clear All</button>` : ""}
|
|
37475
|
-
</div>
|
|
37476
|
-
|
|
37477
|
-
<div class="card">
|
|
37478
|
-
${list}
|
|
37479
|
-
</div>
|
|
37480
|
-
|
|
37481
|
-
<script>
|
|
37482
|
-
async function clearAll() {
|
|
37483
|
-
if (!confirm('Clear all emails?')) return;
|
|
37484
|
-
await fetch('${prefix}', { method: 'DELETE' });
|
|
37485
|
-
window.location.reload();
|
|
37486
|
-
}
|
|
37487
|
-
</script>
|
|
37488
|
-
`;
|
|
37489
|
-
return layout("Inbox", content);
|
|
37490
|
-
}
|
|
37491
|
-
var init_mailbox = __esm({
|
|
37492
|
-
"src/dev/ui/mailbox.ts"() {
|
|
37493
|
-
"use strict";
|
|
37494
|
-
init_shared();
|
|
37495
|
-
}
|
|
37496
|
-
});
|
|
37497
|
-
|
|
37498
|
-
// src/dev/ui/preview.ts
|
|
37499
|
-
function getPreviewHtml(entry, prefix) {
|
|
37500
|
-
const from = entry.envelope.from ? `${entry.envelope.from.name || ""} <${entry.envelope.from.address}>` : "Unknown";
|
|
37501
|
-
const to = entry.envelope.to?.map((t) => t.address).join(", ") || "Unknown";
|
|
37502
|
-
const content = `
|
|
37503
|
-
<div class="header">
|
|
37504
|
-
<div class="title">
|
|
37505
|
-
<a href="${prefix}" class="btn">\u2190 Back</a>
|
|
37506
|
-
<span style="margin-left: 10px">Email Preview</span>
|
|
37507
|
-
</div>
|
|
37508
|
-
<button onclick="deleteEmail('${entry.id}')" class="btn btn-danger">Delete</button>
|
|
37509
|
-
</div>
|
|
37510
|
-
|
|
37511
|
-
<div class="card" style="margin-bottom: 20px; padding: 20px;">
|
|
37512
|
-
<div style="font-size: 18px; font-weight: bold; margin-bottom: 10px;">${entry.envelope.subject || "(No Subject)"}</div>
|
|
37513
|
-
<div class="meta" style="margin-bottom: 5px;">From: ${from}</div>
|
|
37514
|
-
<div class="meta" style="margin-bottom: 5px;">To: ${to}</div>
|
|
37515
|
-
<div class="meta">Date: ${entry.sentAt.toLocaleString()}</div>
|
|
37516
|
-
</div>
|
|
37517
|
-
|
|
37518
|
-
<div style="margin-bottom: 10px;">
|
|
37519
|
-
<button onclick="setView('html')" id="btn-html" class="btn btn-primary">HTML</button>
|
|
37520
|
-
<button onclick="setView('text')" id="btn-text" class="btn">Text</button>
|
|
37521
|
-
<button onclick="setView('raw')" id="btn-raw" class="btn">Raw JSON</button>
|
|
37522
|
-
<a href="${prefix}/${entry.id}/html" target="_blank" class="btn">Open HTML \u2197</a>
|
|
37523
|
-
</div>
|
|
37524
|
-
|
|
37525
|
-
<div class="card" style="height: 600px; display: flex;">
|
|
37526
|
-
<iframe id="preview-frame" src="${prefix}/${entry.id}/html" style="width: 100%; height: 100%; border: none;"></iframe>
|
|
37527
|
-
<pre id="raw-view" style="display: none; padding: 20px; overflow: auto; width: 100%;">${JSON.stringify(entry, null, 2)}</pre>
|
|
37528
|
-
<pre id="text-view" style="display: none; padding: 20px; overflow: auto; width: 100%; white-space: pre-wrap; font-family: monospace;">${entry.text || "No text content"}</pre>
|
|
37529
|
-
</div>
|
|
37530
|
-
|
|
37531
|
-
<script>
|
|
37532
|
-
function setView(mode) {
|
|
37533
|
-
document.getElementById('preview-frame').style.display = mode === 'html' ? 'block' : 'none';
|
|
37534
|
-
document.getElementById('raw-view').style.display = mode === 'raw' ? 'block' : 'none';
|
|
37535
|
-
document.getElementById('text-view').style.display = mode === 'text' ? 'block' : 'none';
|
|
37536
|
-
|
|
37537
|
-
document.getElementById('btn-html').className = mode === 'html' ? 'btn btn-primary' : 'btn';
|
|
37538
|
-
document.getElementById('btn-text').className = mode === 'text' ? 'btn btn-primary' : 'btn';
|
|
37539
|
-
document.getElementById('btn-raw').className = mode === 'raw' ? 'btn btn-primary' : 'btn';
|
|
37540
|
-
}
|
|
37541
|
-
|
|
37542
|
-
async function deleteEmail(id) {
|
|
37543
|
-
if (!confirm('Delete this email?')) return;
|
|
37544
|
-
await fetch('${prefix}/' + id, { method: 'DELETE' });
|
|
37545
|
-
window.location.href = '${prefix}';
|
|
37546
|
-
}
|
|
37547
|
-
</script>
|
|
37548
|
-
`;
|
|
37549
|
-
return layout(entry.envelope.subject || "Preview", content);
|
|
37550
|
-
}
|
|
37551
|
-
var init_preview = __esm({
|
|
37552
|
-
"src/dev/ui/preview.ts"() {
|
|
37553
|
-
"use strict";
|
|
37554
|
-
init_shared();
|
|
37555
|
-
}
|
|
37556
|
-
});
|
|
37557
|
-
|
|
37558
|
-
// src/dev/DevServer.ts
|
|
37559
|
-
var DevServer;
|
|
37560
|
-
var init_DevServer = __esm({
|
|
37561
|
-
"src/dev/DevServer.ts"() {
|
|
37562
|
-
"use strict";
|
|
37563
|
-
init_mailbox();
|
|
37564
|
-
init_preview();
|
|
37565
|
-
DevServer = class {
|
|
37566
|
-
constructor(mailbox, base = "/__mail") {
|
|
37567
|
-
this.mailbox = mailbox;
|
|
37568
|
-
this.base = base;
|
|
37569
|
-
}
|
|
37570
|
-
register(core) {
|
|
37571
|
-
const router = core.router;
|
|
37572
|
-
const prefix = this.base.replace(/\/$/, "");
|
|
37573
|
-
router.get(prefix, (ctx) => {
|
|
37574
|
-
const entries = this.mailbox.list();
|
|
37575
|
-
return ctx.html(getMailboxHtml(entries, prefix));
|
|
37576
|
-
});
|
|
37577
|
-
router.get(`${prefix}/:id`, (ctx) => {
|
|
37578
|
-
const id = ctx.req.param("id");
|
|
37579
|
-
if (!id) {
|
|
37580
|
-
return ctx.text("Bad Request", 400);
|
|
37581
|
-
}
|
|
37582
|
-
const entry = this.mailbox.get(id);
|
|
37583
|
-
if (!entry) {
|
|
37584
|
-
return ctx.text("Email not found", 404);
|
|
37585
|
-
}
|
|
37586
|
-
return ctx.html(getPreviewHtml(entry, prefix));
|
|
37587
|
-
});
|
|
37588
|
-
router.get(`${prefix}/:id/html`, (ctx) => {
|
|
37589
|
-
const id = ctx.req.param("id");
|
|
37590
|
-
if (!id) {
|
|
37591
|
-
return ctx.text("Bad Request", 400);
|
|
37592
|
-
}
|
|
37593
|
-
const entry = this.mailbox.get(id);
|
|
37594
|
-
if (!entry) {
|
|
37595
|
-
return ctx.text("Not found", 404);
|
|
37596
|
-
}
|
|
37597
|
-
return ctx.html(entry.html);
|
|
37598
|
-
});
|
|
37599
|
-
router.get(`${prefix}/:id/text`, (ctx) => {
|
|
37600
|
-
const id = ctx.req.param("id");
|
|
37601
|
-
if (!id) {
|
|
37602
|
-
return ctx.text("Bad Request", 400);
|
|
37603
|
-
}
|
|
37604
|
-
const entry = this.mailbox.get(id);
|
|
37605
|
-
if (!entry) {
|
|
37606
|
-
return ctx.text("Not found", 404);
|
|
37607
|
-
}
|
|
37608
|
-
ctx.header("Content-Type", "text/plain; charset=utf-8");
|
|
37609
|
-
return ctx.text(entry.text || "No text content", 200);
|
|
37610
|
-
});
|
|
37611
|
-
router.get(`${prefix}/:id/raw`, (ctx) => {
|
|
37612
|
-
const id = ctx.req.param("id");
|
|
37613
|
-
if (!id) {
|
|
37614
|
-
return ctx.json({ error: "Bad Request" }, 400);
|
|
37615
|
-
}
|
|
37616
|
-
const entry = this.mailbox.get(id);
|
|
37617
|
-
if (!entry) {
|
|
37618
|
-
return ctx.json({ error: "Not found" }, 404);
|
|
37619
|
-
}
|
|
37620
|
-
return ctx.json(entry);
|
|
37621
|
-
});
|
|
37622
|
-
router.get(`${prefix}/:id/delete`, (ctx) => {
|
|
37623
|
-
return ctx.text("Method not allowed", 405);
|
|
37624
|
-
});
|
|
37625
|
-
router.delete(`${prefix}/:id`, (ctx) => {
|
|
37626
|
-
const id = ctx.req.param("id");
|
|
37627
|
-
if (!id) {
|
|
37628
|
-
return ctx.json({ success: false, error: "Bad Request" }, 400);
|
|
37629
|
-
}
|
|
37630
|
-
const success = this.mailbox.delete(id);
|
|
37631
|
-
return ctx.json({ success });
|
|
37632
|
-
});
|
|
37633
|
-
router.delete(prefix, (ctx) => {
|
|
37634
|
-
this.mailbox.clear();
|
|
37635
|
-
return ctx.json({ success: true });
|
|
37636
|
-
});
|
|
37637
|
-
core.logger.info(`[OrbitSignal] Dev Mailbox available at ${prefix}`);
|
|
37638
|
-
}
|
|
37639
|
-
};
|
|
37640
|
-
}
|
|
37641
|
-
});
|
|
37642
|
-
|
|
37643
|
-
// src/transports/LogTransport.ts
|
|
37644
|
-
var LogTransport;
|
|
37645
|
-
var init_LogTransport = __esm({
|
|
37646
|
-
"src/transports/LogTransport.ts"() {
|
|
37647
|
-
"use strict";
|
|
37648
|
-
LogTransport = class {
|
|
37649
|
-
async send(message) {
|
|
37650
|
-
console.log("\n\u{1F4E7} [OrbitSignal] Email Sent (Simulated):");
|
|
37651
|
-
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
37652
|
-
console.log(
|
|
37653
|
-
`From: ${message.from.name ? `${message.from.name} <${message.from.address}>` : message.from.address}`
|
|
37654
|
-
);
|
|
37655
|
-
console.log(`To: ${message.to.map((t) => t.address).join(", ")}`);
|
|
37656
|
-
console.log(`Subject: ${message.subject}`);
|
|
37657
|
-
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
37658
|
-
console.log(`[Content Size]: ${message.html.length} chars (HTML)`);
|
|
37659
|
-
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
|
|
37660
|
-
}
|
|
37661
|
-
};
|
|
37662
|
-
}
|
|
37663
|
-
});
|
|
37664
|
-
|
|
37665
|
-
// src/transports/MemoryTransport.ts
|
|
37666
|
-
var MemoryTransport;
|
|
37667
|
-
var init_MemoryTransport = __esm({
|
|
37668
|
-
"src/transports/MemoryTransport.ts"() {
|
|
37669
|
-
"use strict";
|
|
37670
|
-
MemoryTransport = class {
|
|
37671
|
-
constructor(mailbox) {
|
|
37672
|
-
this.mailbox = mailbox;
|
|
37673
|
-
}
|
|
37674
|
-
async send(message) {
|
|
37675
|
-
this.mailbox.add(message);
|
|
37676
|
-
}
|
|
37677
|
-
};
|
|
37678
|
-
}
|
|
37679
|
-
});
|
|
37680
|
-
|
|
37681
|
-
// src/OrbitSignal.ts
|
|
37682
|
-
var OrbitSignal_exports = {};
|
|
37683
|
-
__export(OrbitSignal_exports, {
|
|
37684
|
-
OrbitSignal: () => OrbitSignal
|
|
37685
|
-
});
|
|
37686
|
-
var OrbitSignal;
|
|
37687
|
-
var init_OrbitSignal = __esm({
|
|
37688
|
-
"src/OrbitSignal.ts"() {
|
|
37689
|
-
"use strict";
|
|
37690
|
-
init_DevMailbox();
|
|
37691
|
-
init_DevServer();
|
|
37692
|
-
init_LogTransport();
|
|
37693
|
-
init_MemoryTransport();
|
|
37694
|
-
OrbitSignal = class _OrbitSignal {
|
|
37695
|
-
static instance;
|
|
37696
|
-
config;
|
|
37697
|
-
devMailbox;
|
|
37698
|
-
constructor(config = {}) {
|
|
37699
|
-
this.config = config;
|
|
37700
|
-
_OrbitSignal.instance = this;
|
|
37701
|
-
}
|
|
37702
|
-
/**
|
|
37703
|
-
* Get the singleton instance of OrbitSignal
|
|
37704
|
-
*
|
|
37705
|
-
* @returns The singleton instance of OrbitSignal.
|
|
37706
|
-
* @throws {Error} If OrbitSignal has not been initialized.
|
|
37707
|
-
*/
|
|
37708
|
-
static getInstance() {
|
|
37709
|
-
if (!_OrbitSignal.instance) {
|
|
37710
|
-
throw new Error("OrbitSignal has not been initialized. Call OrbitSignal.configure() first.");
|
|
37711
|
-
}
|
|
37712
|
-
return _OrbitSignal.instance;
|
|
37713
|
-
}
|
|
37714
|
-
/**
|
|
37715
|
-
* Configure the OrbitSignal instance
|
|
37716
|
-
*
|
|
37717
|
-
* @param config - The mail configuration object.
|
|
37718
|
-
* @returns A new instance of OrbitSignal.
|
|
37719
|
-
*/
|
|
37720
|
-
static configure(config) {
|
|
37721
|
-
if (!config.transport && !config.devMode) {
|
|
37722
|
-
console.warn("[OrbitSignal] No transport provided, falling back to LogTransport");
|
|
37723
|
-
config.transport = new LogTransport();
|
|
37724
|
-
}
|
|
37725
|
-
return new _OrbitSignal(config);
|
|
37726
|
-
}
|
|
37727
|
-
/**
|
|
37728
|
-
* Install the orbit into PlanetCore
|
|
37729
|
-
*
|
|
37730
|
-
* @param core - The PlanetCore instance.
|
|
37731
|
-
*/
|
|
37732
|
-
install(core) {
|
|
37733
|
-
core.logger.info("[OrbitSignal] Initializing Mail Service (Exposed as: mail)");
|
|
37734
|
-
if (!this.config.transport && !this.config.devMode) {
|
|
37735
|
-
this.config.transport = new LogTransport();
|
|
37736
|
-
}
|
|
37737
|
-
if (this.config.devMode) {
|
|
37738
|
-
this.devMailbox = new DevMailbox();
|
|
37739
|
-
this.config.transport = new MemoryTransport(this.devMailbox);
|
|
37740
|
-
core.logger.info("[OrbitSignal] Dev Mode Enabled: Emails will be intercepted to Dev Mailbox");
|
|
37741
|
-
const devServer = new DevServer(this.devMailbox, this.config.devUiPrefix || "/__mail");
|
|
37742
|
-
devServer.register(core);
|
|
37743
|
-
}
|
|
37744
|
-
core.adapter.use("*", async (c, next) => {
|
|
37745
|
-
c.set("mail", {
|
|
37746
|
-
send: (mailable) => this.send(mailable),
|
|
37747
|
-
queue: (mailable) => this.queue(mailable)
|
|
37748
|
-
});
|
|
37749
|
-
await next();
|
|
37750
|
-
return void 0;
|
|
37751
|
-
});
|
|
37752
|
-
}
|
|
37753
|
-
/**
|
|
37754
|
-
* Send a mailable instance
|
|
37755
|
-
*
|
|
37756
|
-
* @param mailable - The mailable object to send.
|
|
37757
|
-
* @returns A promise that resolves when the email is sent.
|
|
37758
|
-
* @throws {Error} If the message is missing "from" or "to" addresses, or if no transport is configured.
|
|
37759
|
-
*/
|
|
37760
|
-
async send(mailable) {
|
|
37761
|
-
const envelope = await mailable.buildEnvelope(this.config);
|
|
37762
|
-
if (!envelope.from) {
|
|
37763
|
-
throw new Error('Message is missing "from" address');
|
|
37764
|
-
}
|
|
37765
|
-
if (!envelope.to || envelope.to.length === 0) {
|
|
37766
|
-
throw new Error('Message is missing "to" address');
|
|
37767
|
-
}
|
|
37768
|
-
const content = await mailable.renderContent();
|
|
37769
|
-
const message = {
|
|
37770
|
-
...envelope,
|
|
37771
|
-
from: envelope.from,
|
|
37772
|
-
to: envelope.to,
|
|
37773
|
-
subject: envelope.subject || "(No Subject)",
|
|
37774
|
-
priority: envelope.priority || "normal",
|
|
37775
|
-
html: content.html
|
|
37776
|
-
};
|
|
37777
|
-
if (content.text) {
|
|
37778
|
-
message.text = content.text;
|
|
37779
|
-
}
|
|
37780
|
-
if (!this.config.transport) {
|
|
37781
|
-
throw new Error(
|
|
37782
|
-
"[OrbitSignal] No transport configured. Did you call configure() or register the orbit?"
|
|
37783
|
-
);
|
|
37784
|
-
}
|
|
37785
|
-
await this.config.transport.send(message);
|
|
37786
|
-
}
|
|
37787
|
-
/**
|
|
37788
|
-
* Queue a mailable instance
|
|
37789
|
-
*
|
|
37790
|
-
* Push a mailable into the queue for execution.
|
|
37791
|
-
* Requires OrbitStream to be installed and available in the context.
|
|
37792
|
-
*
|
|
37793
|
-
* @param mailable - The mailable object to queue.
|
|
37794
|
-
* @returns A promise that resolves when the job is pushed to the queue or sent immediately if no queue service is found.
|
|
37795
|
-
*/
|
|
37796
|
-
async queue(mailable) {
|
|
37797
|
-
const queue = this.queueService;
|
|
37798
|
-
if (queue) {
|
|
37799
|
-
await queue.push(mailable);
|
|
37800
|
-
} else {
|
|
37801
|
-
console.warn(
|
|
37802
|
-
"[OrbitSignal] Queue service not available, sending immediately. Install OrbitStream to enable queuing."
|
|
37803
|
-
);
|
|
37804
|
-
await this.send(mailable);
|
|
37805
|
-
}
|
|
37806
|
-
}
|
|
37807
|
-
};
|
|
37808
|
-
}
|
|
37809
|
-
});
|
|
37810
|
-
|
|
37811
37331
|
// src/index.ts
|
|
37812
37332
|
var index_exports = {};
|
|
37813
37333
|
__export(index_exports, {
|
|
@@ -37822,7 +37342,53 @@ __export(index_exports, {
|
|
|
37822
37342
|
TemplateRenderer: () => TemplateRenderer
|
|
37823
37343
|
});
|
|
37824
37344
|
module.exports = __toCommonJS(index_exports);
|
|
37825
|
-
|
|
37345
|
+
|
|
37346
|
+
// src/dev/DevMailbox.ts
|
|
37347
|
+
var import_node_crypto = require("crypto");
|
|
37348
|
+
var DevMailbox = class {
|
|
37349
|
+
entries = [];
|
|
37350
|
+
maxEntries = 50;
|
|
37351
|
+
add(message) {
|
|
37352
|
+
const entry = {
|
|
37353
|
+
id: (0, import_node_crypto.randomUUID)(),
|
|
37354
|
+
envelope: {
|
|
37355
|
+
from: message.from,
|
|
37356
|
+
to: message.to,
|
|
37357
|
+
...message.cc ? { cc: message.cc } : {},
|
|
37358
|
+
...message.bcc ? { bcc: message.bcc } : {},
|
|
37359
|
+
...message.replyTo ? { replyTo: message.replyTo } : {},
|
|
37360
|
+
subject: message.subject,
|
|
37361
|
+
...message.priority ? { priority: message.priority } : {},
|
|
37362
|
+
...message.attachments ? { attachments: message.attachments } : {}
|
|
37363
|
+
},
|
|
37364
|
+
html: message.html,
|
|
37365
|
+
...message.text ? { text: message.text } : {},
|
|
37366
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
37367
|
+
};
|
|
37368
|
+
this.entries.unshift(entry);
|
|
37369
|
+
if (this.entries.length > this.maxEntries) {
|
|
37370
|
+
this.entries = this.entries.slice(0, this.maxEntries);
|
|
37371
|
+
}
|
|
37372
|
+
return entry;
|
|
37373
|
+
}
|
|
37374
|
+
list() {
|
|
37375
|
+
return this.entries;
|
|
37376
|
+
}
|
|
37377
|
+
get(id) {
|
|
37378
|
+
return this.entries.find((e) => e.id === id);
|
|
37379
|
+
}
|
|
37380
|
+
delete(id) {
|
|
37381
|
+
const index = this.entries.findIndex((e) => e.id === id);
|
|
37382
|
+
if (index !== -1) {
|
|
37383
|
+
this.entries.splice(index, 1);
|
|
37384
|
+
return true;
|
|
37385
|
+
}
|
|
37386
|
+
return false;
|
|
37387
|
+
}
|
|
37388
|
+
clear() {
|
|
37389
|
+
this.entries = [];
|
|
37390
|
+
}
|
|
37391
|
+
};
|
|
37826
37392
|
|
|
37827
37393
|
// src/renderers/HtmlRenderer.ts
|
|
37828
37394
|
var HtmlRenderer = class {
|
|
@@ -37868,6 +37434,7 @@ var Mailable = class {
|
|
|
37868
37434
|
renderer;
|
|
37869
37435
|
rendererResolver;
|
|
37870
37436
|
renderData = {};
|
|
37437
|
+
config;
|
|
37871
37438
|
// ===== Fluent API (Envelope Construction) =====
|
|
37872
37439
|
from(address) {
|
|
37873
37440
|
this.envelope.from = typeof address === "string" ? { address } : address;
|
|
@@ -37893,7 +37460,7 @@ var Mailable = class {
|
|
|
37893
37460
|
this.envelope.subject = subject;
|
|
37894
37461
|
return this;
|
|
37895
37462
|
}
|
|
37896
|
-
|
|
37463
|
+
emailPriority(level) {
|
|
37897
37464
|
this.envelope.priority = level;
|
|
37898
37465
|
return this;
|
|
37899
37466
|
}
|
|
@@ -37924,10 +37491,10 @@ var Mailable = class {
|
|
|
37924
37491
|
* Set the content using a React component.
|
|
37925
37492
|
* Dynamically imports ReactRenderer to avoid hard dependency errors if React is not installed.
|
|
37926
37493
|
*/
|
|
37927
|
-
react(component, props) {
|
|
37494
|
+
react(component, props, deps) {
|
|
37928
37495
|
this.rendererResolver = async () => {
|
|
37929
37496
|
const { ReactRenderer: ReactRenderer2 } = await Promise.resolve().then(() => (init_ReactRenderer(), ReactRenderer_exports));
|
|
37930
|
-
return new ReactRenderer2(component, props);
|
|
37497
|
+
return new ReactRenderer2(component, props, deps);
|
|
37931
37498
|
};
|
|
37932
37499
|
return this;
|
|
37933
37500
|
}
|
|
@@ -37935,10 +37502,10 @@ var Mailable = class {
|
|
|
37935
37502
|
* Set the content using a Vue component.
|
|
37936
37503
|
* Dynamically imports VueRenderer to avoid hard dependency errors if Vue is not installed.
|
|
37937
37504
|
*/
|
|
37938
|
-
vue(component, props) {
|
|
37505
|
+
vue(component, props, deps) {
|
|
37939
37506
|
this.rendererResolver = async () => {
|
|
37940
37507
|
const { VueRenderer: VueRenderer2 } = await Promise.resolve().then(() => (init_VueRenderer(), VueRenderer_exports));
|
|
37941
|
-
return new VueRenderer2(component, props);
|
|
37508
|
+
return new VueRenderer2(component, props, deps);
|
|
37942
37509
|
};
|
|
37943
37510
|
return this;
|
|
37944
37511
|
}
|
|
@@ -37946,6 +37513,7 @@ var Mailable = class {
|
|
|
37946
37513
|
queueName;
|
|
37947
37514
|
connectionName;
|
|
37948
37515
|
delaySeconds;
|
|
37516
|
+
priority;
|
|
37949
37517
|
onQueue(queue) {
|
|
37950
37518
|
this.queueName = queue;
|
|
37951
37519
|
return this;
|
|
@@ -37958,12 +37526,23 @@ var Mailable = class {
|
|
|
37958
37526
|
this.delaySeconds = seconds;
|
|
37959
37527
|
return this;
|
|
37960
37528
|
}
|
|
37529
|
+
withPriority(priority) {
|
|
37530
|
+
this.priority = priority;
|
|
37531
|
+
return this;
|
|
37532
|
+
}
|
|
37961
37533
|
/**
|
|
37962
37534
|
* Queue the mailable for sending.
|
|
37963
37535
|
*/
|
|
37964
37536
|
async queue() {
|
|
37965
|
-
|
|
37966
|
-
|
|
37537
|
+
try {
|
|
37538
|
+
const { app } = await import("@gravito/core");
|
|
37539
|
+
const mail = app().container.make("mail");
|
|
37540
|
+
if (mail) {
|
|
37541
|
+
return mail.queue(this);
|
|
37542
|
+
}
|
|
37543
|
+
} catch (_e) {
|
|
37544
|
+
console.warn("[Mailable] Could not auto-resolve mail service for queuing.");
|
|
37545
|
+
}
|
|
37967
37546
|
}
|
|
37968
37547
|
// ===== I18n Support =====
|
|
37969
37548
|
currentLocale;
|
|
@@ -37996,11 +37575,13 @@ var Mailable = class {
|
|
|
37996
37575
|
*/
|
|
37997
37576
|
async buildEnvelope(configPromise) {
|
|
37998
37577
|
const config = await Promise.resolve(configPromise);
|
|
37578
|
+
this.config = config;
|
|
37999
37579
|
if (config.translator) {
|
|
38000
37580
|
this.setTranslator(config.translator);
|
|
38001
37581
|
}
|
|
38002
37582
|
this.build();
|
|
38003
37583
|
if (this.renderer instanceof TemplateRenderer && config.viewsDir) {
|
|
37584
|
+
this.renderer = new TemplateRenderer(this.renderer.template, config.viewsDir);
|
|
38004
37585
|
}
|
|
38005
37586
|
const envelope = {
|
|
38006
37587
|
from: this.envelope.from || config.from,
|
|
@@ -38045,10 +37626,427 @@ var Mailable = class {
|
|
|
38045
37626
|
}
|
|
38046
37627
|
};
|
|
38047
37628
|
|
|
38048
|
-
// src/
|
|
38049
|
-
|
|
38050
|
-
|
|
38051
|
-
|
|
37629
|
+
// src/dev/ui/shared.ts
|
|
37630
|
+
var styles = `
|
|
37631
|
+
:root {
|
|
37632
|
+
--primary: #6366f1;
|
|
37633
|
+
--primary-dark: #4f46e5;
|
|
37634
|
+
--bg-dark: #0f172a;
|
|
37635
|
+
--bg-card: #1e293b;
|
|
37636
|
+
--text: #f1f5f9;
|
|
37637
|
+
--text-muted: #94a3b8;
|
|
37638
|
+
--border: #334155;
|
|
37639
|
+
--danger: #ef4444;
|
|
37640
|
+
}
|
|
37641
|
+
body { background: var(--bg-dark); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 20px; }
|
|
37642
|
+
.container { max-width: 1000px; margin: 0 auto; }
|
|
37643
|
+
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
|
37644
|
+
.title { font-size: 24px; font-weight: bold; display: flex; align-items: center; gap: 10px; }
|
|
37645
|
+
.card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
|
|
37646
|
+
.btn { background: var(--border); color: var(--text); border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; text-decoration: none; font-size: 14px; transition: 0.2s; }
|
|
37647
|
+
.btn:hover { background: var(--bg-card-hover); }
|
|
37648
|
+
.btn-primary { background: var(--primary); color: white; }
|
|
37649
|
+
.btn-primary:hover { background: var(--primary-dark); }
|
|
37650
|
+
.btn-danger { background: var(--danger); color: white; }
|
|
37651
|
+
.list-item { padding: 16px; border-bottom: 1px solid var(--border); display: flex; flex-direction: column; gap: 4px; text-decoration: none; color: inherit; transition: background 0.2s; }
|
|
37652
|
+
.list-item:last-child { border-bottom: none; }
|
|
37653
|
+
.list-item:hover { background: #334155; }
|
|
37654
|
+
.meta { display: flex; justify-content: space-between; font-size: 14px; color: var(--text-muted); }
|
|
37655
|
+
.subject { font-weight: 600; font-size: 16px; }
|
|
37656
|
+
.from { color: #cbd5e1; }
|
|
37657
|
+
.badge { background: #475569; padding: 2px 6px; border-radius: 4px; font-size: 12px; }
|
|
37658
|
+
.badge-high { background: #dc2626; color: white; }
|
|
37659
|
+
.empty { padding: 40px; text-align: center; color: var(--text-muted); }
|
|
37660
|
+
`;
|
|
37661
|
+
var layout = (title, content) => `
|
|
37662
|
+
<!DOCTYPE html>
|
|
37663
|
+
<html>
|
|
37664
|
+
<head>
|
|
37665
|
+
<meta charset="utf-8">
|
|
37666
|
+
<title>${title} - Gravito Mailbox</title>
|
|
37667
|
+
<style>${styles}</style>
|
|
37668
|
+
</head>
|
|
37669
|
+
<body>
|
|
37670
|
+
<div class="container">
|
|
37671
|
+
${content}
|
|
37672
|
+
</div>
|
|
37673
|
+
</body>
|
|
37674
|
+
</html>
|
|
37675
|
+
`;
|
|
37676
|
+
|
|
37677
|
+
// src/dev/ui/mailbox.ts
|
|
37678
|
+
function formatAddress(addr) {
|
|
37679
|
+
return addr.name ? `${addr.name} <${addr.address}>` : addr.address;
|
|
37680
|
+
}
|
|
37681
|
+
function timeAgo(date) {
|
|
37682
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
37683
|
+
if (seconds < 60) {
|
|
37684
|
+
return "Just now";
|
|
37685
|
+
}
|
|
37686
|
+
const minutes = Math.floor(seconds / 60);
|
|
37687
|
+
if (minutes < 60) {
|
|
37688
|
+
return `${minutes}m ago`;
|
|
37689
|
+
}
|
|
37690
|
+
const hours = Math.floor(minutes / 60);
|
|
37691
|
+
if (hours < 24) {
|
|
37692
|
+
return `${hours}h ago`;
|
|
37693
|
+
}
|
|
37694
|
+
return date.toLocaleDateString();
|
|
37695
|
+
}
|
|
37696
|
+
function getMailboxHtml(entries, prefix) {
|
|
37697
|
+
const list = entries.length === 0 ? '<div class="empty">No emails found in mailbox.</div>' : entries.map(
|
|
37698
|
+
(entry) => `
|
|
37699
|
+
<a href="${prefix}/${entry.id}" class="list-item">
|
|
37700
|
+
<div class="meta">
|
|
37701
|
+
<span class="from">${formatAddress(entry.envelope.from || { address: "Unknown" })}</span>
|
|
37702
|
+
<span>${timeAgo(entry.sentAt)}</span>
|
|
37703
|
+
</div>
|
|
37704
|
+
<div class="subject">
|
|
37705
|
+
${entry.envelope.priority === "high" ? '<span class="badge badge-high">High</span> ' : ""}
|
|
37706
|
+
${entry.envelope.subject || "(No Subject)"}
|
|
37707
|
+
</div>
|
|
37708
|
+
<div class="meta" style="margin-top: 4px;">
|
|
37709
|
+
To: ${entry.envelope.to?.map((t) => t.address).join(", ")}
|
|
37710
|
+
</div>
|
|
37711
|
+
</a>
|
|
37712
|
+
`
|
|
37713
|
+
).join("");
|
|
37714
|
+
const content = `
|
|
37715
|
+
<div class="header">
|
|
37716
|
+
<div class="title">\u{1F4EC} Gravito Mailbox</div>
|
|
37717
|
+
${entries.length > 0 ? `<button onclick="clearAll()" class="btn btn-danger">Clear All</button>` : ""}
|
|
37718
|
+
</div>
|
|
37719
|
+
|
|
37720
|
+
<div class="card">
|
|
37721
|
+
${list}
|
|
37722
|
+
</div>
|
|
37723
|
+
|
|
37724
|
+
<script>
|
|
37725
|
+
async function clearAll() {
|
|
37726
|
+
if (!confirm('Clear all emails?')) return;
|
|
37727
|
+
await fetch('${prefix}', { method: 'DELETE' });
|
|
37728
|
+
window.location.reload();
|
|
37729
|
+
}
|
|
37730
|
+
</script>
|
|
37731
|
+
`;
|
|
37732
|
+
return layout("Inbox", content);
|
|
37733
|
+
}
|
|
37734
|
+
|
|
37735
|
+
// src/dev/ui/preview.ts
|
|
37736
|
+
function getPreviewHtml(entry, prefix) {
|
|
37737
|
+
const from = entry.envelope.from ? `${entry.envelope.from.name || ""} <${entry.envelope.from.address}>` : "Unknown";
|
|
37738
|
+
const to = entry.envelope.to?.map((t) => t.address).join(", ") || "Unknown";
|
|
37739
|
+
const content = `
|
|
37740
|
+
<div class="header">
|
|
37741
|
+
<div class="title">
|
|
37742
|
+
<a href="${prefix}" class="btn">\u2190 Back</a>
|
|
37743
|
+
<span style="margin-left: 10px">Email Preview</span>
|
|
37744
|
+
</div>
|
|
37745
|
+
<button onclick="deleteEmail('${entry.id}')" class="btn btn-danger">Delete</button>
|
|
37746
|
+
</div>
|
|
37747
|
+
|
|
37748
|
+
<div class="card" style="margin-bottom: 20px; padding: 20px;">
|
|
37749
|
+
<div style="font-size: 18px; font-weight: bold; margin-bottom: 10px;">${entry.envelope.subject || "(No Subject)"}</div>
|
|
37750
|
+
<div class="meta" style="margin-bottom: 5px;">From: ${from}</div>
|
|
37751
|
+
<div class="meta" style="margin-bottom: 5px;">To: ${to}</div>
|
|
37752
|
+
${entry.envelope.cc ? `<div class="meta" style="margin-bottom: 5px;">CC: ${entry.envelope.cc.map((t) => t.address).join(", ")}</div>` : ""}
|
|
37753
|
+
${entry.envelope.bcc ? `<div class="meta" style="margin-bottom: 5px;">BCC: ${entry.envelope.bcc.map((t) => t.address).join(", ")}</div>` : ""}
|
|
37754
|
+
${entry.envelope.priority ? `<div class="meta" style="margin-bottom: 5px;">Priority: ${entry.envelope.priority}</div>` : ""}
|
|
37755
|
+
<div class="meta">Date: ${entry.sentAt.toLocaleString()}</div>
|
|
37756
|
+
${entry.envelope.attachments && entry.envelope.attachments.length > 0 ? `
|
|
37757
|
+
<div style="margin-top: 15px; border-top: 1px solid var(--border); padding-top: 10px;">
|
|
37758
|
+
<div style="font-size: 14px; font-weight: bold; margin-bottom: 5px; color: var(--text-muted);">Attachments (${entry.envelope.attachments.length})</div>
|
|
37759
|
+
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
|
37760
|
+
${entry.envelope.attachments.map(
|
|
37761
|
+
(att) => `
|
|
37762
|
+
<div style="background: var(--bg-dark); padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border); display: flex; align-items: center; gap: 8px;">
|
|
37763
|
+
<span style="font-size: 20px;">\u{1F4CE}</span>
|
|
37764
|
+
<div>
|
|
37765
|
+
<div style="font-size: 14px; font-weight: 500;">${att.filename || "untitled"}</div>
|
|
37766
|
+
<div style="font-size: 12px; color: var(--text-muted);">${att.contentType || "application/octet-stream"}</div>
|
|
37767
|
+
</div>
|
|
37768
|
+
</div>
|
|
37769
|
+
`
|
|
37770
|
+
).join("")}
|
|
37771
|
+
</div>
|
|
37772
|
+
</div>
|
|
37773
|
+
` : ""}
|
|
37774
|
+
</div>
|
|
37775
|
+
|
|
37776
|
+
<div style="margin-bottom: 10px;">
|
|
37777
|
+
<button onclick="setView('html')" id="btn-html" class="btn btn-primary">HTML</button>
|
|
37778
|
+
<button onclick="setView('text')" id="btn-text" class="btn">Text</button>
|
|
37779
|
+
<button onclick="setView('raw')" id="btn-raw" class="btn">Raw JSON</button>
|
|
37780
|
+
<a href="${prefix}/${entry.id}/html" target="_blank" class="btn">Open HTML \u2197</a>
|
|
37781
|
+
</div>
|
|
37782
|
+
|
|
37783
|
+
<div class="card" style="height: 600px; display: flex;">
|
|
37784
|
+
<iframe id="preview-frame" src="${prefix}/${entry.id}/html" style="width: 100%; height: 100%; border: none;"></iframe>
|
|
37785
|
+
<pre id="raw-view" style="display: none; padding: 20px; overflow: auto; width: 100%;">${JSON.stringify(entry, null, 2)}</pre>
|
|
37786
|
+
<pre id="text-view" style="display: none; padding: 20px; overflow: auto; width: 100%; white-space: pre-wrap; font-family: monospace;">${entry.text || "No text content"}</pre>
|
|
37787
|
+
</div>
|
|
37788
|
+
|
|
37789
|
+
<script>
|
|
37790
|
+
function setView(mode) {
|
|
37791
|
+
document.getElementById('preview-frame').style.display = mode === 'html' ? 'block' : 'none';
|
|
37792
|
+
document.getElementById('raw-view').style.display = mode === 'raw' ? 'block' : 'none';
|
|
37793
|
+
document.getElementById('text-view').style.display = mode === 'text' ? 'block' : 'none';
|
|
37794
|
+
|
|
37795
|
+
document.getElementById('btn-html').className = mode === 'html' ? 'btn btn-primary' : 'btn';
|
|
37796
|
+
document.getElementById('btn-text').className = mode === 'text' ? 'btn btn-primary' : 'btn';
|
|
37797
|
+
document.getElementById('btn-raw').className = mode === 'raw' ? 'btn btn-primary' : 'btn';
|
|
37798
|
+
}
|
|
37799
|
+
|
|
37800
|
+
async function deleteEmail(id) {
|
|
37801
|
+
if (!confirm('Delete this email?')) return;
|
|
37802
|
+
await fetch('${prefix}/' + id, { method: 'DELETE' });
|
|
37803
|
+
window.location.href = '${prefix}';
|
|
37804
|
+
}
|
|
37805
|
+
</script>
|
|
37806
|
+
`;
|
|
37807
|
+
return layout(entry.envelope.subject || "Preview", content);
|
|
37808
|
+
}
|
|
37809
|
+
|
|
37810
|
+
// src/dev/DevServer.ts
|
|
37811
|
+
var DevServer = class {
|
|
37812
|
+
constructor(mailbox, base = "/__mail", options) {
|
|
37813
|
+
this.mailbox = mailbox;
|
|
37814
|
+
this.base = base;
|
|
37815
|
+
this.options = options;
|
|
37816
|
+
}
|
|
37817
|
+
async canAccess(ctx) {
|
|
37818
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
37819
|
+
if (isProduction && !this.options?.allowInProduction && !this.options?.gate) {
|
|
37820
|
+
return false;
|
|
37821
|
+
}
|
|
37822
|
+
if (this.options?.gate) {
|
|
37823
|
+
return await this.options.gate(ctx);
|
|
37824
|
+
}
|
|
37825
|
+
return true;
|
|
37826
|
+
}
|
|
37827
|
+
register(core) {
|
|
37828
|
+
const router = core.router;
|
|
37829
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
37830
|
+
if (isProduction && !this.options?.allowInProduction && !this.options?.gate) {
|
|
37831
|
+
core.logger.warn(
|
|
37832
|
+
"[OrbitSignal] Dev Mailbox disabled in production. Configure a gate to allow access."
|
|
37833
|
+
);
|
|
37834
|
+
return;
|
|
37835
|
+
}
|
|
37836
|
+
const prefix = this.base.replace(/\/$/, "");
|
|
37837
|
+
const wrap = (handler) => {
|
|
37838
|
+
return async (ctx) => {
|
|
37839
|
+
const allowed = await this.canAccess(ctx);
|
|
37840
|
+
if (!allowed) {
|
|
37841
|
+
return ctx.text("Unauthorized", 403);
|
|
37842
|
+
}
|
|
37843
|
+
return await handler(ctx);
|
|
37844
|
+
};
|
|
37845
|
+
};
|
|
37846
|
+
router.get(
|
|
37847
|
+
prefix,
|
|
37848
|
+
wrap((ctx) => {
|
|
37849
|
+
const entries = this.mailbox.list();
|
|
37850
|
+
ctx.header("Content-Type", "text/html; charset=utf-8");
|
|
37851
|
+
return ctx.html(getMailboxHtml(entries, prefix));
|
|
37852
|
+
})
|
|
37853
|
+
);
|
|
37854
|
+
router.get(
|
|
37855
|
+
`${prefix}/:id`,
|
|
37856
|
+
wrap((ctx) => {
|
|
37857
|
+
const id = ctx.req.param("id");
|
|
37858
|
+
if (!id) {
|
|
37859
|
+
return ctx.text("Bad Request", 400);
|
|
37860
|
+
}
|
|
37861
|
+
const entry = this.mailbox.get(id);
|
|
37862
|
+
if (!entry) {
|
|
37863
|
+
return ctx.text("Email not found", 404);
|
|
37864
|
+
}
|
|
37865
|
+
ctx.header("Content-Type", "text/html; charset=utf-8");
|
|
37866
|
+
return ctx.html(getPreviewHtml(entry, prefix));
|
|
37867
|
+
})
|
|
37868
|
+
);
|
|
37869
|
+
router.get(
|
|
37870
|
+
`${prefix}/:id/html`,
|
|
37871
|
+
wrap((ctx) => {
|
|
37872
|
+
const id = ctx.req.param("id");
|
|
37873
|
+
if (!id) {
|
|
37874
|
+
return ctx.text("Bad Request", 400);
|
|
37875
|
+
}
|
|
37876
|
+
const entry = this.mailbox.get(id);
|
|
37877
|
+
if (!entry) {
|
|
37878
|
+
return ctx.text("Not found", 404);
|
|
37879
|
+
}
|
|
37880
|
+
ctx.header("Content-Type", "text/html; charset=utf-8");
|
|
37881
|
+
return ctx.html(entry.html);
|
|
37882
|
+
})
|
|
37883
|
+
);
|
|
37884
|
+
router.get(
|
|
37885
|
+
`${prefix}/:id/text`,
|
|
37886
|
+
wrap((ctx) => {
|
|
37887
|
+
const id = ctx.req.param("id");
|
|
37888
|
+
if (!id) {
|
|
37889
|
+
return ctx.text("Bad Request", 400);
|
|
37890
|
+
}
|
|
37891
|
+
const entry = this.mailbox.get(id);
|
|
37892
|
+
if (!entry) {
|
|
37893
|
+
return ctx.text("Not found", 404);
|
|
37894
|
+
}
|
|
37895
|
+
ctx.header("Content-Type", "text/plain; charset=utf-8");
|
|
37896
|
+
return ctx.text(entry.text || "No text content", 200);
|
|
37897
|
+
})
|
|
37898
|
+
);
|
|
37899
|
+
router.get(
|
|
37900
|
+
`${prefix}/:id/raw`,
|
|
37901
|
+
wrap((ctx) => {
|
|
37902
|
+
const id = ctx.req.param("id");
|
|
37903
|
+
if (!id) {
|
|
37904
|
+
return ctx.json({ error: "Bad Request" }, 400);
|
|
37905
|
+
}
|
|
37906
|
+
const entry = this.mailbox.get(id);
|
|
37907
|
+
if (!entry) {
|
|
37908
|
+
return ctx.json({ error: "Not found" }, 404);
|
|
37909
|
+
}
|
|
37910
|
+
return ctx.json(entry);
|
|
37911
|
+
})
|
|
37912
|
+
);
|
|
37913
|
+
router.get(
|
|
37914
|
+
`${prefix}/:id/delete`,
|
|
37915
|
+
wrap((ctx) => {
|
|
37916
|
+
return ctx.text("Method not allowed", 405);
|
|
37917
|
+
})
|
|
37918
|
+
);
|
|
37919
|
+
router.delete(
|
|
37920
|
+
`${prefix}/:id`,
|
|
37921
|
+
wrap((ctx) => {
|
|
37922
|
+
const id = ctx.req.param("id");
|
|
37923
|
+
if (!id) {
|
|
37924
|
+
return ctx.json({ success: false, error: "Bad Request" }, 400);
|
|
37925
|
+
}
|
|
37926
|
+
const success = this.mailbox.delete(id);
|
|
37927
|
+
return ctx.json({ success });
|
|
37928
|
+
})
|
|
37929
|
+
);
|
|
37930
|
+
router.delete(
|
|
37931
|
+
prefix,
|
|
37932
|
+
wrap((ctx) => {
|
|
37933
|
+
this.mailbox.clear();
|
|
37934
|
+
return ctx.json({ success: true });
|
|
37935
|
+
})
|
|
37936
|
+
);
|
|
37937
|
+
core.logger.info(`[OrbitSignal] Dev Mailbox available at ${prefix}`);
|
|
37938
|
+
}
|
|
37939
|
+
};
|
|
37940
|
+
|
|
37941
|
+
// src/transports/LogTransport.ts
|
|
37942
|
+
var LogTransport = class {
|
|
37943
|
+
async send(message) {
|
|
37944
|
+
console.log("\n\u{1F4E7} [OrbitSignal] Email Sent (Simulated):");
|
|
37945
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
37946
|
+
console.log(
|
|
37947
|
+
`From: ${message.from.name ? `${message.from.name} <${message.from.address}>` : message.from.address}`
|
|
37948
|
+
);
|
|
37949
|
+
console.log(`To: ${message.to.map((t) => t.address).join(", ")}`);
|
|
37950
|
+
console.log(`Subject: ${message.subject}`);
|
|
37951
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
37952
|
+
console.log(`[Content Size]: ${message.html.length} chars (HTML)`);
|
|
37953
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
|
|
37954
|
+
}
|
|
37955
|
+
};
|
|
37956
|
+
|
|
37957
|
+
// src/transports/MemoryTransport.ts
|
|
37958
|
+
var MemoryTransport = class {
|
|
37959
|
+
constructor(mailbox) {
|
|
37960
|
+
this.mailbox = mailbox;
|
|
37961
|
+
}
|
|
37962
|
+
async send(message) {
|
|
37963
|
+
this.mailbox.add(message);
|
|
37964
|
+
}
|
|
37965
|
+
};
|
|
37966
|
+
|
|
37967
|
+
// src/OrbitSignal.ts
|
|
37968
|
+
var OrbitSignal = class {
|
|
37969
|
+
config;
|
|
37970
|
+
devMailbox;
|
|
37971
|
+
core;
|
|
37972
|
+
constructor(config = {}) {
|
|
37973
|
+
this.config = config;
|
|
37974
|
+
}
|
|
37975
|
+
/**
|
|
37976
|
+
* Install the orbit into PlanetCore
|
|
37977
|
+
*
|
|
37978
|
+
* @param core - The PlanetCore instance.
|
|
37979
|
+
*/
|
|
37980
|
+
install(core) {
|
|
37981
|
+
this.core = core;
|
|
37982
|
+
core.logger.info("[OrbitSignal] Initializing Mail Service");
|
|
37983
|
+
if (!this.config.transport && !this.config.devMode) {
|
|
37984
|
+
this.config.transport = new LogTransport();
|
|
37985
|
+
}
|
|
37986
|
+
if (this.config.devMode) {
|
|
37987
|
+
this.devMailbox = new DevMailbox();
|
|
37988
|
+
this.config.transport = new MemoryTransport(this.devMailbox);
|
|
37989
|
+
core.logger.info("[OrbitSignal] Dev Mode Enabled: Emails will be intercepted to Dev Mailbox");
|
|
37990
|
+
const devServer = new DevServer(this.devMailbox, this.config.devUiPrefix || "/__mail", {
|
|
37991
|
+
allowInProduction: this.config.devUiAllowInProduction,
|
|
37992
|
+
gate: this.config.devUiGate
|
|
37993
|
+
});
|
|
37994
|
+
devServer.register(core);
|
|
37995
|
+
}
|
|
37996
|
+
core.container.singleton("mail", () => this);
|
|
37997
|
+
core.adapter.use("*", async (c, next) => {
|
|
37998
|
+
c.set("mail", this);
|
|
37999
|
+
await next();
|
|
38000
|
+
return void 0;
|
|
38001
|
+
});
|
|
38002
|
+
}
|
|
38003
|
+
/**
|
|
38004
|
+
* Send a mailable instance
|
|
38005
|
+
*
|
|
38006
|
+
* @param mailable - The mailable object to send.
|
|
38007
|
+
* @returns A promise that resolves when the email is sent.
|
|
38008
|
+
* @throws {Error} If the message is missing "from" or "to" addresses, or if no transport is configured.
|
|
38009
|
+
*/
|
|
38010
|
+
async send(mailable) {
|
|
38011
|
+
const envelope = await mailable.buildEnvelope(this.config);
|
|
38012
|
+
if (!envelope.from) {
|
|
38013
|
+
throw new Error('Message is missing "from" address');
|
|
38014
|
+
}
|
|
38015
|
+
if (!envelope.to || envelope.to.length === 0) {
|
|
38016
|
+
throw new Error('Message is missing "to" address');
|
|
38017
|
+
}
|
|
38018
|
+
const content = await mailable.renderContent();
|
|
38019
|
+
const message = {
|
|
38020
|
+
...envelope,
|
|
38021
|
+
from: envelope.from,
|
|
38022
|
+
to: envelope.to,
|
|
38023
|
+
subject: envelope.subject || "(No Subject)",
|
|
38024
|
+
priority: envelope.priority || "normal",
|
|
38025
|
+
html: content.html
|
|
38026
|
+
};
|
|
38027
|
+
if (content.text) {
|
|
38028
|
+
message.text = content.text;
|
|
38029
|
+
}
|
|
38030
|
+
if (!this.config.transport) {
|
|
38031
|
+
throw new Error("[OrbitSignal] No transport configured. Did you call register the orbit?");
|
|
38032
|
+
}
|
|
38033
|
+
await this.config.transport.send(message);
|
|
38034
|
+
}
|
|
38035
|
+
/**
|
|
38036
|
+
* Queue a mailable instance
|
|
38037
|
+
*/
|
|
38038
|
+
async queue(mailable) {
|
|
38039
|
+
try {
|
|
38040
|
+
const queue = this.core?.container.make("queue");
|
|
38041
|
+
if (queue) {
|
|
38042
|
+
await queue.push(mailable);
|
|
38043
|
+
return;
|
|
38044
|
+
}
|
|
38045
|
+
} catch (_e) {
|
|
38046
|
+
}
|
|
38047
|
+
await this.send(mailable);
|
|
38048
|
+
}
|
|
38049
|
+
};
|
|
38052
38050
|
|
|
38053
38051
|
// src/transports/SesTransport.ts
|
|
38054
38052
|
var import_client_ses = require("@aws-sdk/client-ses");
|