@gravito/signal 3.0.4 → 3.1.2
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/README.md +26 -15
- package/dist/Mailable.d.ts +453 -0
- package/dist/OrbitSignal.d.ts +267 -0
- package/{src/Queueable.ts → dist/Queueable.d.ts} +1 -1
- package/{src/TypedMailable.ts → dist/TypedMailable.d.ts} +12 -13
- package/dist/dev/DevMailbox.d.ts +79 -0
- package/dist/dev/DevServer.d.ts +39 -0
- package/dist/dev/storage/FileMailboxStorage.d.ts +16 -0
- package/dist/dev/storage/MailboxStorage.d.ts +14 -0
- package/dist/dev/storage/MemoryMailboxStorage.d.ts +13 -0
- package/dist/dev/ui/mailbox.d.ts +11 -0
- package/dist/dev/ui/preview.d.ts +11 -0
- package/dist/dev/ui/shared.d.ts +15 -0
- package/dist/errors.d.ts +58 -0
- package/{src/events.ts → dist/events.d.ts} +20 -29
- package/dist/{lib-HJTRWKU5.mjs → index.cjs} +4687 -10983
- package/dist/index.d.ts +303 -1874
- package/dist/index.js +317 -45939
- package/dist/index.mjs +59748 -27
- package/dist/renderers/HtmlRenderer.d.ts +34 -0
- package/dist/renderers/MjmlRenderer.d.ts +41 -0
- package/dist/renderers/ReactMjmlRenderer.d.ts +38 -0
- package/dist/renderers/ReactRenderer.d.ts +46 -0
- package/{src/renderers/Renderer.ts → dist/renderers/Renderer.d.ts} +23 -24
- package/dist/renderers/TemplateRenderer.d.ts +55 -0
- package/dist/renderers/VueMjmlRenderer.d.ts +39 -0
- package/dist/renderers/VueRenderer.d.ts +47 -0
- package/dist/renderers/mjml-templates.d.ts +11 -0
- package/dist/transports/BaseTransport.d.ts +111 -0
- package/dist/transports/LogTransport.d.ts +42 -0
- package/dist/transports/MemoryTransport.d.ts +51 -0
- package/dist/transports/SesTransport.d.ts +79 -0
- package/dist/transports/SmtpTransport.d.ts +124 -0
- package/dist/transports/Transport.d.ts +44 -0
- package/dist/types.d.ts +294 -0
- package/{src/utils/html.ts → dist/utils/html.d.ts} +1 -15
- package/dist/webhooks/SendGridWebhookDriver.d.ts +45 -0
- package/dist/webhooks/SesWebhookDriver.d.ts +23 -0
- package/package.json +20 -8
- package/CHANGELOG.md +0 -74
- package/dist/MjmlRenderer-IUH663FT.mjs +0 -8
- package/dist/ReactMjmlRenderer-C3P5YO5L.mjs +0 -8
- package/dist/ReactRenderer-24SQ4KRU.mjs +0 -27
- package/dist/ReactRenderer-2JFLRVST.mjs +0 -45
- package/dist/ReactRenderer-CMCAOEPH.mjs +0 -28
- package/dist/ReactRenderer-KYNA4WKE.mjs +0 -28
- package/dist/ReactRenderer-LYEOSYFS.mjs +0 -28
- package/dist/ReactRenderer-V54CUUEI.mjs +0 -45
- package/dist/VueMjmlRenderer-4F4CXHDB.mjs +0 -8
- package/dist/VueMjmlRenderer-5WZR4CQG.mjs +0 -8
- package/dist/VueMjmlRenderer-U5YMWI44.mjs +0 -8
- package/dist/VueRenderer-3YBRQXME.mjs +0 -48
- package/dist/VueRenderer-46JGXTJ2.mjs +0 -48
- package/dist/VueRenderer-5KWD4R3C.mjs +0 -48
- package/dist/VueRenderer-C23U4O5E.mjs +0 -48
- package/dist/VueRenderer-DWTCD2RF.mjs +0 -31
- package/dist/VueRenderer-IIR5SYTM.mjs +0 -31
- package/dist/VueRenderer-LEVDFLHP.mjs +0 -31
- package/dist/VueRenderer-RNHSCCRI.mjs +0 -48
- package/dist/VueRenderer-SUP66ISX.mjs +0 -29
- package/dist/chunk-3WOR3XSL.mjs +0 -82
- package/dist/chunk-DBFIVHHG.mjs +0 -79
- package/dist/chunk-EBO3CZXG.mjs +0 -15
- package/dist/chunk-HEBXNMVQ.mjs +0 -48
- package/dist/chunk-KB7IDDBT.mjs +0 -82
- package/dist/chunk-LZL5UUPC.mjs +0 -82
- package/dist/chunk-W6LXIJKK.mjs +0 -57
- package/dist/chunk-XBIVBJS2.mjs +0 -8
- package/dist/index.d.mts +0 -2115
- package/dist/server-renderer-4IM3P5XZ.mjs +0 -37183
- package/dist/server-renderer-4W4FI7YG.mjs +0 -37269
- package/dist/server-renderer-7KWFSTPV.mjs +0 -37193
- package/dist/server-renderer-S5FPSTJ2.mjs +0 -37183
- package/dist/server-renderer-X5LUFVWT.mjs +0 -37193
- package/doc/OPTIMIZATION_PLAN.md +0 -496
- package/scripts/check-coverage.ts +0 -64
- package/src/Mailable.ts +0 -674
- package/src/OrbitSignal.ts +0 -451
- package/src/dev/DevMailbox.ts +0 -146
- package/src/dev/DevServer.ts +0 -192
- package/src/dev/storage/FileMailboxStorage.ts +0 -66
- package/src/dev/storage/MailboxStorage.ts +0 -15
- package/src/dev/storage/MemoryMailboxStorage.ts +0 -36
- package/src/dev/ui/mailbox.ts +0 -77
- package/src/dev/ui/preview.ts +0 -103
- package/src/dev/ui/shared.ts +0 -60
- package/src/errors.ts +0 -69
- package/src/index.ts +0 -41
- package/src/renderers/HtmlRenderer.ts +0 -41
- package/src/renderers/MjmlRenderer.ts +0 -73
- package/src/renderers/ReactMjmlRenderer.ts +0 -94
- package/src/renderers/ReactRenderer.ts +0 -66
- package/src/renderers/TemplateRenderer.ts +0 -84
- package/src/renderers/VueMjmlRenderer.ts +0 -99
- package/src/renderers/VueRenderer.ts +0 -71
- package/src/renderers/mjml-templates.ts +0 -50
- package/src/transports/BaseTransport.ts +0 -148
- package/src/transports/LogTransport.ts +0 -55
- package/src/transports/MemoryTransport.ts +0 -55
- package/src/transports/SesTransport.ts +0 -129
- package/src/transports/SmtpTransport.ts +0 -184
- package/src/transports/Transport.ts +0 -45
- package/src/types.ts +0 -309
- package/src/webhooks/SendGridWebhookDriver.ts +0 -80
- package/src/webhooks/SesWebhookDriver.ts +0 -44
- package/tests/DevMailbox.test.ts +0 -54
- package/tests/FileMailboxStorage.test.ts +0 -56
- package/tests/MjmlLayout.test.ts +0 -28
- package/tests/MjmlRenderer.test.ts +0 -53
- package/tests/OrbitSignalWebhook.test.ts +0 -56
- package/tests/ReactMjmlRenderer.test.ts +0 -33
- package/tests/SendGridWebhookDriver.test.ts +0 -69
- package/tests/SesWebhookDriver.test.ts +0 -46
- package/tests/VueMjmlRenderer.test.ts +0 -35
- package/tests/dev-server.test.ts +0 -66
- package/tests/log-transport.test.ts +0 -21
- package/tests/mailable-extra.test.ts +0 -68
- package/tests/mailable.test.ts +0 -77
- package/tests/orbit-signal.test.ts +0 -43
- package/tests/renderers.test.ts +0 -58
- package/tests/template-renderer.test.ts +0 -24
- package/tests/transports.test.ts +0 -52
- package/tests/ui.test.ts +0 -37
- package/tsconfig.json +0 -14
package/src/dev/DevServer.ts
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import type { GravitoContext, PlanetCore } from '@gravito/core'
|
|
2
|
-
import type { DevMailbox } from './DevMailbox'
|
|
3
|
-
import { getMailboxHtml } from './ui/mailbox'
|
|
4
|
-
import { getPreviewHtml } from './ui/preview'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Configuration options for the development mail server.
|
|
8
|
-
*
|
|
9
|
-
* @public
|
|
10
|
-
*/
|
|
11
|
-
export type DevServerOptions = {
|
|
12
|
-
/** Allow access in production environment (use with caution) */
|
|
13
|
-
allowInProduction?: boolean
|
|
14
|
-
/** Custom gate function to control access to the dev UI */
|
|
15
|
-
gate?: (c: GravitoContext) => boolean | Promise<boolean>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Development mail server for previewing captured emails.
|
|
20
|
-
*
|
|
21
|
-
* Provides a web UI at the configured base path (default: `/__mail`)
|
|
22
|
-
* to view, preview, and manage emails captured during development.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```typescript
|
|
26
|
-
* const mailbox = new DevMailbox()
|
|
27
|
-
* const devServer = new DevServer(mailbox, '/__mail', {
|
|
28
|
-
* gate: (c) => c.get('user')?.isAdmin
|
|
29
|
-
* })
|
|
30
|
-
* devServer.register(core)
|
|
31
|
-
* ```
|
|
32
|
-
*
|
|
33
|
-
* @since 3.0.0
|
|
34
|
-
* @public
|
|
35
|
-
*/
|
|
36
|
-
export class DevServer {
|
|
37
|
-
constructor(
|
|
38
|
-
private mailbox: DevMailbox,
|
|
39
|
-
private base = '/__mail',
|
|
40
|
-
private options?: DevServerOptions
|
|
41
|
-
) {}
|
|
42
|
-
|
|
43
|
-
private async canAccess(ctx: GravitoContext): Promise<boolean> {
|
|
44
|
-
const isProduction = process.env.NODE_ENV === 'production'
|
|
45
|
-
if (isProduction && !this.options?.allowInProduction && !this.options?.gate) {
|
|
46
|
-
return false
|
|
47
|
-
}
|
|
48
|
-
if (this.options?.gate) {
|
|
49
|
-
return await this.options.gate(ctx)
|
|
50
|
-
}
|
|
51
|
-
return true
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
register(core: PlanetCore): void {
|
|
55
|
-
const router = core.router
|
|
56
|
-
const isProduction = process.env.NODE_ENV === 'production'
|
|
57
|
-
|
|
58
|
-
if (isProduction && !this.options?.allowInProduction && !this.options?.gate) {
|
|
59
|
-
core.logger.warn(
|
|
60
|
-
'[OrbitSignal] Dev Mailbox disabled in production. Configure a gate to allow access.'
|
|
61
|
-
)
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Remove trailing slash
|
|
66
|
-
const prefix = this.base.replace(/\/$/, '')
|
|
67
|
-
|
|
68
|
-
const wrap = (handler: (ctx: GravitoContext) => Response | Promise<Response>) => {
|
|
69
|
-
return async (ctx: GravitoContext) => {
|
|
70
|
-
const allowed = await this.canAccess(ctx)
|
|
71
|
-
if (!allowed) {
|
|
72
|
-
return ctx.text('Unauthorized', 403)
|
|
73
|
-
}
|
|
74
|
-
return await handler(ctx)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// 1. Mailbox List
|
|
79
|
-
router.get(
|
|
80
|
-
prefix,
|
|
81
|
-
wrap(async (ctx) => {
|
|
82
|
-
const entries = await this.mailbox.list()
|
|
83
|
-
ctx.header('Content-Type', 'text/html; charset=utf-8')
|
|
84
|
-
return ctx.html(getMailboxHtml(entries, prefix))
|
|
85
|
-
})
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
// 2. Single Email Preview
|
|
89
|
-
router.get(
|
|
90
|
-
`${prefix}/:id`,
|
|
91
|
-
wrap(async (ctx) => {
|
|
92
|
-
const id = ctx.req.param('id')
|
|
93
|
-
if (!id) {
|
|
94
|
-
return ctx.text('Bad Request', 400)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const entry = await this.mailbox.get(id)
|
|
98
|
-
if (!entry) {
|
|
99
|
-
return ctx.text('Email not found', 404)
|
|
100
|
-
}
|
|
101
|
-
ctx.header('Content-Type', 'text/html; charset=utf-8')
|
|
102
|
-
return ctx.html(getPreviewHtml(entry, prefix))
|
|
103
|
-
})
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
// 3. Iframe Content: HTML
|
|
107
|
-
router.get(
|
|
108
|
-
`${prefix}/:id/html`,
|
|
109
|
-
wrap(async (ctx) => {
|
|
110
|
-
const id = ctx.req.param('id')
|
|
111
|
-
if (!id) {
|
|
112
|
-
return ctx.text('Bad Request', 400)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const entry = await this.mailbox.get(id)
|
|
116
|
-
if (!entry) {
|
|
117
|
-
return ctx.text('Not found', 404)
|
|
118
|
-
}
|
|
119
|
-
ctx.header('Content-Type', 'text/html; charset=utf-8')
|
|
120
|
-
return ctx.html(entry.html)
|
|
121
|
-
})
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
// 4. Iframe Content: Text
|
|
125
|
-
router.get(
|
|
126
|
-
`${prefix}/:id/text`,
|
|
127
|
-
wrap(async (ctx) => {
|
|
128
|
-
const id = ctx.req.param('id')
|
|
129
|
-
if (!id) {
|
|
130
|
-
return ctx.text('Bad Request', 400)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const entry = await this.mailbox.get(id)
|
|
134
|
-
if (!entry) {
|
|
135
|
-
return ctx.text('Not found', 404)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
ctx.header('Content-Type', 'text/plain; charset=utf-8')
|
|
139
|
-
return ctx.text(entry.text || 'No text content', 200)
|
|
140
|
-
})
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
// 5. Raw JSON
|
|
144
|
-
router.get(
|
|
145
|
-
`${prefix}/:id/raw`,
|
|
146
|
-
wrap(async (ctx) => {
|
|
147
|
-
const id = ctx.req.param('id')
|
|
148
|
-
if (!id) {
|
|
149
|
-
return ctx.json({ error: 'Bad Request' }, 400)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const entry = await this.mailbox.get(id)
|
|
153
|
-
if (!entry) {
|
|
154
|
-
return ctx.json({ error: 'Not found' }, 404)
|
|
155
|
-
}
|
|
156
|
-
return ctx.json(entry)
|
|
157
|
-
})
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
// 6. API: Delete Single
|
|
161
|
-
router.get(
|
|
162
|
-
`${prefix}/:id/delete`,
|
|
163
|
-
wrap((ctx) => {
|
|
164
|
-
return ctx.text('Method not allowed', 405)
|
|
165
|
-
})
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
router.delete(
|
|
169
|
-
`${prefix}/:id`,
|
|
170
|
-
wrap(async (ctx) => {
|
|
171
|
-
const id = ctx.req.param('id')
|
|
172
|
-
if (!id) {
|
|
173
|
-
return ctx.json({ success: false, error: 'Bad Request' }, 400)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const success = await this.mailbox.delete(id)
|
|
177
|
-
return ctx.json({ success })
|
|
178
|
-
})
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
// 7. API: Clear All
|
|
182
|
-
router.delete(
|
|
183
|
-
prefix,
|
|
184
|
-
wrap(async (ctx) => {
|
|
185
|
-
await this.mailbox.clear()
|
|
186
|
-
return ctx.json({ success: true })
|
|
187
|
-
})
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
core.logger.info(`[OrbitSignal] Dev Mailbox available at ${prefix}`)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
import type { MailboxEntry } from '../DevMailbox'
|
|
4
|
-
import type { MailboxStorage } from './MailboxStorage'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* File-based storage for DevMailbox (Persistence).
|
|
8
|
-
* Stores emails as JSON in a specified directory.
|
|
9
|
-
*/
|
|
10
|
-
export class FileMailboxStorage implements MailboxStorage {
|
|
11
|
-
private filePath: string
|
|
12
|
-
|
|
13
|
-
constructor(storageDir: string) {
|
|
14
|
-
this.filePath = join(storageDir, 'mailbox.json')
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async all(): Promise<MailboxEntry[]> {
|
|
18
|
-
try {
|
|
19
|
-
const content = await readFile(this.filePath, 'utf-8')
|
|
20
|
-
return JSON.parse(content)
|
|
21
|
-
} catch (_e) {
|
|
22
|
-
return []
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async push(entry: MailboxEntry): Promise<void> {
|
|
27
|
-
const entries = await this.all()
|
|
28
|
-
entries.unshift(entry)
|
|
29
|
-
await this.save(entries)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async trim(max: number): Promise<void> {
|
|
33
|
-
const entries = await this.all()
|
|
34
|
-
if (entries.length > max) {
|
|
35
|
-
await this.save(entries.slice(0, max))
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async clear(): Promise<void> {
|
|
40
|
-
try {
|
|
41
|
-
await unlink(this.filePath)
|
|
42
|
-
} catch (_e) {
|
|
43
|
-
// Ignore if file doesn't exist
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async delete(id: string): Promise<boolean> {
|
|
48
|
-
const entries = await this.all()
|
|
49
|
-
const index = entries.findIndex((e) => e.id === id)
|
|
50
|
-
if (index !== -1) {
|
|
51
|
-
entries.splice(index, 1)
|
|
52
|
-
await this.save(entries)
|
|
53
|
-
return true
|
|
54
|
-
}
|
|
55
|
-
return false
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private async save(entries: MailboxEntry[]): Promise<void> {
|
|
59
|
-
try {
|
|
60
|
-
await mkdir(join(this.filePath, '..'), { recursive: true })
|
|
61
|
-
await writeFile(this.filePath, JSON.stringify(entries, null, 2), 'utf-8')
|
|
62
|
-
} catch (error) {
|
|
63
|
-
console.error('[FileMailboxStorage] Failed to save mailbox:', error)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { MailboxEntry } from '../DevMailbox'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Interface for DevMailbox storage engines.
|
|
5
|
-
*/
|
|
6
|
-
export interface MailboxStorage {
|
|
7
|
-
/** Retrieve all entries. */
|
|
8
|
-
all(): Promise<MailboxEntry[]>
|
|
9
|
-
/** Add a single entry. */
|
|
10
|
-
push(entry: MailboxEntry): Promise<void>
|
|
11
|
-
/** Trim entries to a specific count. */
|
|
12
|
-
trim(max: number): Promise<void>
|
|
13
|
-
clear(): Promise<void>
|
|
14
|
-
delete?(id: string): Promise<boolean>
|
|
15
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { MailboxEntry } from '../DevMailbox'
|
|
2
|
-
import type { MailboxStorage } from './MailboxStorage'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Memory-based storage for DevMailbox (Default).
|
|
6
|
-
*/
|
|
7
|
-
export class MemoryMailboxStorage implements MailboxStorage {
|
|
8
|
-
private entries: MailboxEntry[] = []
|
|
9
|
-
|
|
10
|
-
async all(): Promise<MailboxEntry[]> {
|
|
11
|
-
return [...this.entries]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async push(entry: MailboxEntry): Promise<void> {
|
|
15
|
-
this.entries.unshift(entry)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async trim(max: number): Promise<void> {
|
|
19
|
-
if (this.entries.length > max) {
|
|
20
|
-
this.entries = this.entries.slice(0, max)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async clear(): Promise<void> {
|
|
25
|
-
this.entries = []
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async delete(id: string): Promise<boolean> {
|
|
29
|
-
const index = this.entries.findIndex((e) => e.id === id)
|
|
30
|
-
if (index !== -1) {
|
|
31
|
-
this.entries.splice(index, 1)
|
|
32
|
-
return true
|
|
33
|
-
}
|
|
34
|
-
return false
|
|
35
|
-
}
|
|
36
|
-
}
|
package/src/dev/ui/mailbox.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import type { MailboxEntry } from '../DevMailbox'
|
|
2
|
-
import { layout } from './shared'
|
|
3
|
-
|
|
4
|
-
function formatAddress(addr: { name?: string; address: string }): string {
|
|
5
|
-
return addr.name ? `${addr.name} <${addr.address}>` : addr.address
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function timeAgo(date: Date): string {
|
|
9
|
-
const seconds = Math.floor((Date.now() - date.getTime()) / 1000)
|
|
10
|
-
if (seconds < 60) {
|
|
11
|
-
return 'Just now'
|
|
12
|
-
}
|
|
13
|
-
const minutes = Math.floor(seconds / 60)
|
|
14
|
-
if (minutes < 60) {
|
|
15
|
-
return `${minutes}m ago`
|
|
16
|
-
}
|
|
17
|
-
const hours = Math.floor(minutes / 60)
|
|
18
|
-
if (hours < 24) {
|
|
19
|
-
return `${hours}h ago`
|
|
20
|
-
}
|
|
21
|
-
return date.toLocaleDateString()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Generates the HTML for the mailbox list view.
|
|
26
|
-
*
|
|
27
|
-
* @param entries - Array of mailbox entries to display
|
|
28
|
-
* @param prefix - Base URL prefix for the dev server
|
|
29
|
-
* @returns HTML string for the mailbox UI
|
|
30
|
-
*
|
|
31
|
-
* @internal
|
|
32
|
-
*/
|
|
33
|
-
export function getMailboxHtml(entries: MailboxEntry[], prefix: string): string {
|
|
34
|
-
const list =
|
|
35
|
-
entries.length === 0
|
|
36
|
-
? '<div class="empty">No emails found in mailbox.</div>'
|
|
37
|
-
: entries
|
|
38
|
-
.map(
|
|
39
|
-
(entry) => `
|
|
40
|
-
<a href="${prefix}/${entry.id}" class="list-item">
|
|
41
|
-
<div class="meta">
|
|
42
|
-
<span class="from">${formatAddress(entry.envelope.from || { address: 'Unknown' })}</span>
|
|
43
|
-
<span>${timeAgo(entry.sentAt)}</span>
|
|
44
|
-
</div>
|
|
45
|
-
<div class="subject">
|
|
46
|
-
${entry.envelope.priority === 'high' ? '<span class="badge badge-high">High</span> ' : ''}
|
|
47
|
-
${entry.envelope.subject || '(No Subject)'}
|
|
48
|
-
</div>
|
|
49
|
-
<div class="meta" style="margin-top: 4px;">
|
|
50
|
-
To: ${entry.envelope.to?.map((t: any) => t.address).join(', ')}
|
|
51
|
-
</div>
|
|
52
|
-
</a>
|
|
53
|
-
`
|
|
54
|
-
)
|
|
55
|
-
.join('')
|
|
56
|
-
|
|
57
|
-
const content = `
|
|
58
|
-
<div class="header">
|
|
59
|
-
<div class="title">📬 Gravito Mailbox</div>
|
|
60
|
-
${entries.length > 0 ? `<button onclick="clearAll()" class="btn btn-danger">Clear All</button>` : ''}
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
<div class="card">
|
|
64
|
-
${list}
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<script>
|
|
68
|
-
async function clearAll() {
|
|
69
|
-
if (!confirm('Clear all emails?')) return;
|
|
70
|
-
await fetch('${prefix}', { method: 'DELETE' });
|
|
71
|
-
window.location.reload();
|
|
72
|
-
}
|
|
73
|
-
</script>
|
|
74
|
-
`
|
|
75
|
-
|
|
76
|
-
return layout('Inbox', content)
|
|
77
|
-
}
|
package/src/dev/ui/preview.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import type { MailboxEntry } from '../DevMailbox'
|
|
2
|
-
import { layout } from './shared'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Generates the HTML for the email preview page.
|
|
6
|
-
*
|
|
7
|
-
* @param entry - Mailbox entry to preview
|
|
8
|
-
* @param prefix - Base URL prefix for the dev server
|
|
9
|
-
* @returns HTML string for the preview UI
|
|
10
|
-
*
|
|
11
|
-
* @internal
|
|
12
|
-
*/
|
|
13
|
-
export function getPreviewHtml(entry: MailboxEntry, prefix: string): string {
|
|
14
|
-
const from = entry.envelope.from
|
|
15
|
-
? `${entry.envelope.from.name || ''} <${entry.envelope.from.address}>`
|
|
16
|
-
: 'Unknown'
|
|
17
|
-
const to = entry.envelope.to?.map((t: any) => t.address).join(', ') || 'Unknown'
|
|
18
|
-
|
|
19
|
-
const content = `
|
|
20
|
-
<div class="header">
|
|
21
|
-
<div class="title">
|
|
22
|
-
<a href="${prefix}" class="btn">← Back</a>
|
|
23
|
-
<span style="margin-left: 10px">Email Preview</span>
|
|
24
|
-
</div>
|
|
25
|
-
<button onclick="deleteEmail('${entry.id}')" class="btn btn-danger">Delete</button>
|
|
26
|
-
</div>
|
|
27
|
-
|
|
28
|
-
<div class="card" style="margin-bottom: 20px; padding: 20px;">
|
|
29
|
-
<div style="font-size: 18px; font-weight: bold; margin-bottom: 10px;">${entry.envelope.subject || '(No Subject)'}</div>
|
|
30
|
-
<div class="meta" style="margin-bottom: 5px;">From: ${from}</div>
|
|
31
|
-
<div class="meta" style="margin-bottom: 5px;">To: ${to}</div>
|
|
32
|
-
${
|
|
33
|
-
entry.envelope.cc
|
|
34
|
-
? `<div class="meta" style="margin-bottom: 5px;">CC: ${entry.envelope.cc.map((t: any) => t.address).join(', ')}</div>`
|
|
35
|
-
: ''
|
|
36
|
-
}
|
|
37
|
-
${
|
|
38
|
-
entry.envelope.bcc
|
|
39
|
-
? `<div class="meta" style="margin-bottom: 5px;">BCC: ${entry.envelope.bcc.map((t: any) => t.address).join(', ')}</div>`
|
|
40
|
-
: ''
|
|
41
|
-
}
|
|
42
|
-
${entry.envelope.priority ? `<div class="meta" style="margin-bottom: 5px;">Priority: ${entry.envelope.priority}</div>` : ''}
|
|
43
|
-
<div class="meta">Date: ${entry.sentAt.toLocaleString()}</div>
|
|
44
|
-
${
|
|
45
|
-
entry.envelope.attachments && entry.envelope.attachments.length > 0
|
|
46
|
-
? `
|
|
47
|
-
<div style="margin-top: 15px; border-top: 1px solid var(--border); padding-top: 10px;">
|
|
48
|
-
<div style="font-size: 14px; font-weight: bold; margin-bottom: 5px; color: var(--text-muted);">Attachments (${entry.envelope.attachments.length})</div>
|
|
49
|
-
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
|
50
|
-
${entry.envelope.attachments
|
|
51
|
-
.map(
|
|
52
|
-
(att: any) => `
|
|
53
|
-
<div style="background: var(--bg-dark); padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border); display: flex; align-items: center; gap: 8px;">
|
|
54
|
-
<span style="font-size: 20px;">📎</span>
|
|
55
|
-
<div>
|
|
56
|
-
<div style="font-size: 14px; font-weight: 500;">${att.filename || 'untitled'}</div>
|
|
57
|
-
<div style="font-size: 12px; color: var(--text-muted);">${att.contentType || 'application/octet-stream'}</div>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
`
|
|
61
|
-
)
|
|
62
|
-
.join('')}
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
`
|
|
66
|
-
: ''
|
|
67
|
-
}
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<div style="margin-bottom: 10px;">
|
|
71
|
-
<button onclick="setView('html')" id="btn-html" class="btn btn-primary">HTML</button>
|
|
72
|
-
<button onclick="setView('text')" id="btn-text" class="btn">Text</button>
|
|
73
|
-
<button onclick="setView('raw')" id="btn-raw" class="btn">Raw JSON</button>
|
|
74
|
-
<a href="${prefix}/${entry.id}/html" target="_blank" class="btn">Open HTML ↗</a>
|
|
75
|
-
</div>
|
|
76
|
-
|
|
77
|
-
<div class="card" style="height: 600px; display: flex;">
|
|
78
|
-
<iframe id="preview-frame" src="${prefix}/${entry.id}/html" style="width: 100%; height: 100%; border: none;"></iframe>
|
|
79
|
-
<pre id="raw-view" style="display: none; padding: 20px; overflow: auto; width: 100%;">${JSON.stringify(entry, null, 2)}</pre>
|
|
80
|
-
<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>
|
|
81
|
-
</div>
|
|
82
|
-
|
|
83
|
-
<script>
|
|
84
|
-
function setView(mode) {
|
|
85
|
-
document.getElementById('preview-frame').style.display = mode === 'html' ? 'block' : 'none';
|
|
86
|
-
document.getElementById('raw-view').style.display = mode === 'raw' ? 'block' : 'none';
|
|
87
|
-
document.getElementById('text-view').style.display = mode === 'text' ? 'block' : 'none';
|
|
88
|
-
|
|
89
|
-
document.getElementById('btn-html').className = mode === 'html' ? 'btn btn-primary' : 'btn';
|
|
90
|
-
document.getElementById('btn-text').className = mode === 'text' ? 'btn btn-primary' : 'btn';
|
|
91
|
-
document.getElementById('btn-raw').className = mode === 'raw' ? 'btn btn-primary' : 'btn';
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function deleteEmail(id) {
|
|
95
|
-
if (!confirm('Delete this email?')) return;
|
|
96
|
-
await fetch('${prefix}/' + id, { method: 'DELETE' });
|
|
97
|
-
window.location.href = '${prefix}';
|
|
98
|
-
}
|
|
99
|
-
</script>
|
|
100
|
-
`
|
|
101
|
-
|
|
102
|
-
return layout(entry.envelope.subject || 'Preview', content)
|
|
103
|
-
}
|
package/src/dev/ui/shared.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CSS styles for the development mail UI.
|
|
3
|
-
* @internal
|
|
4
|
-
*/
|
|
5
|
-
export const styles = `
|
|
6
|
-
:root {
|
|
7
|
-
--primary: #6366f1;
|
|
8
|
-
--primary-dark: #4f46e5;
|
|
9
|
-
--bg-dark: #0f172a;
|
|
10
|
-
--bg-card: #1e293b;
|
|
11
|
-
--text: #f1f5f9;
|
|
12
|
-
--text-muted: #94a3b8;
|
|
13
|
-
--border: #334155;
|
|
14
|
-
--danger: #ef4444;
|
|
15
|
-
}
|
|
16
|
-
body { background: var(--bg-dark); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 20px; }
|
|
17
|
-
.container { max-width: 1000px; margin: 0 auto; }
|
|
18
|
-
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
|
19
|
-
.title { font-size: 24px; font-weight: bold; display: flex; align-items: center; gap: 10px; }
|
|
20
|
-
.card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
|
|
21
|
-
.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; }
|
|
22
|
-
.btn:hover { background: var(--bg-card-hover); }
|
|
23
|
-
.btn-primary { background: var(--primary); color: white; }
|
|
24
|
-
.btn-primary:hover { background: var(--primary-dark); }
|
|
25
|
-
.btn-danger { background: var(--danger); color: white; }
|
|
26
|
-
.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; }
|
|
27
|
-
.list-item:last-child { border-bottom: none; }
|
|
28
|
-
.list-item:hover { background: #334155; }
|
|
29
|
-
.meta { display: flex; justify-content: space-between; font-size: 14px; color: var(--text-muted); }
|
|
30
|
-
.subject { font-weight: 600; font-size: 16px; }
|
|
31
|
-
.from { color: #cbd5e1; }
|
|
32
|
-
.badge { background: #475569; padding: 2px 6px; border-radius: 4px; font-size: 12px; }
|
|
33
|
-
.badge-high { background: #dc2626; color: white; }
|
|
34
|
-
.empty { padding: 40px; text-align: center; color: var(--text-muted); }
|
|
35
|
-
`
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* HTML layout wrapper for dev mail UI pages.
|
|
39
|
-
*
|
|
40
|
-
* @param title - Page title
|
|
41
|
-
* @param content - HTML content to inject
|
|
42
|
-
* @returns Complete HTML document
|
|
43
|
-
*
|
|
44
|
-
* @internal
|
|
45
|
-
*/
|
|
46
|
-
export const layout = (title: string, content: string) => `
|
|
47
|
-
<!DOCTYPE html>
|
|
48
|
-
<html>
|
|
49
|
-
<head>
|
|
50
|
-
<meta charset="utf-8">
|
|
51
|
-
<title>${title} - Gravito Mailbox</title>
|
|
52
|
-
<style>${styles}</style>
|
|
53
|
-
</head>
|
|
54
|
-
<body>
|
|
55
|
-
<div class="container">
|
|
56
|
-
${content}
|
|
57
|
-
</div>
|
|
58
|
-
</body>
|
|
59
|
-
</html>
|
|
60
|
-
`
|
package/src/errors.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mail transport error codes.
|
|
3
|
-
*
|
|
4
|
-
* Categorizes common failure modes in the mail delivery process to allow
|
|
5
|
-
* for programmatic handling (e.g., retries on rate limits).
|
|
6
|
-
*
|
|
7
|
-
* @public
|
|
8
|
-
* @since 3.1.0
|
|
9
|
-
*/
|
|
10
|
-
export enum MailErrorCode {
|
|
11
|
-
/** Connection to mail server failed */
|
|
12
|
-
CONNECTION_FAILED = 'CONNECTION_FAILED',
|
|
13
|
-
/** Authentication with mail server failed */
|
|
14
|
-
AUTH_FAILED = 'AUTH_FAILED',
|
|
15
|
-
/** One or more recipients were rejected by the server */
|
|
16
|
-
RECIPIENT_REJECTED = 'RECIPIENT_REJECTED',
|
|
17
|
-
/** The message was rejected by the server */
|
|
18
|
-
MESSAGE_REJECTED = 'MESSAGE_REJECTED',
|
|
19
|
-
/** Rate limit exceeded */
|
|
20
|
-
RATE_LIMIT = 'RATE_LIMIT',
|
|
21
|
-
/** Unknown or unclassified error */
|
|
22
|
-
UNKNOWN = 'UNKNOWN',
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Error class for mail transport failures.
|
|
27
|
-
*
|
|
28
|
-
* Provides structured error information for mail sending failures,
|
|
29
|
-
* including error codes and original cause tracking for debugging.
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```typescript
|
|
33
|
-
* throw new MailTransportError(
|
|
34
|
-
* 'Failed to connect to SMTP server',
|
|
35
|
-
* MailErrorCode.CONNECTION_FAILED,
|
|
36
|
-
* originalError
|
|
37
|
-
* )
|
|
38
|
-
* ```
|
|
39
|
-
*
|
|
40
|
-
* @public
|
|
41
|
-
* @since 3.1.0
|
|
42
|
-
*/
|
|
43
|
-
export class MailTransportError extends Error {
|
|
44
|
-
/**
|
|
45
|
-
* Create a new mail transport error.
|
|
46
|
-
*
|
|
47
|
-
* @param message - Human-readable error message
|
|
48
|
-
* @param code - Categorized error code
|
|
49
|
-
* @param cause - Original error that caused this failure
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* ```typescript
|
|
53
|
-
* const error = new MailTransportError('Auth failed', MailErrorCode.AUTH_FAILED);
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
constructor(
|
|
57
|
-
message: string,
|
|
58
|
-
public readonly code: MailErrorCode,
|
|
59
|
-
public readonly cause?: Error
|
|
60
|
-
) {
|
|
61
|
-
super(message)
|
|
62
|
-
this.name = 'MailTransportError'
|
|
63
|
-
|
|
64
|
-
// Maintain proper stack trace in V8 environments
|
|
65
|
-
if (Error.captureStackTrace) {
|
|
66
|
-
Error.captureStackTrace(this, MailTransportError)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OrbitSignal - The central Event Bus and Mail Service for Gravito.
|
|
3
|
-
*
|
|
4
|
-
* This module provides the core infrastructure for sending emails and managing
|
|
5
|
-
* cross-module signals within the Gravito ecosystem. It supports multiple
|
|
6
|
-
* transport drivers, template rendering, and a robust development environment.
|
|
7
|
-
*
|
|
8
|
-
* @packageDocumentation
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// import './augmentation'
|
|
12
|
-
|
|
13
|
-
export type { Queueable } from '@gravito/stream'
|
|
14
|
-
export { DevMailbox, type MailboxEntry } from './dev/DevMailbox'
|
|
15
|
-
export { MailErrorCode, MailTransportError } from './errors'
|
|
16
|
-
export type { MailEvent, MailEventHandler, MailEventType } from './events'
|
|
17
|
-
export { Mailable } from './Mailable'
|
|
18
|
-
export { OrbitSignal } from './OrbitSignal'
|
|
19
|
-
export { HtmlRenderer } from './renderers/HtmlRenderer'
|
|
20
|
-
export { MjmlRenderer } from './renderers/MjmlRenderer'
|
|
21
|
-
export * from './renderers/mjml-templates'
|
|
22
|
-
export { ReactMjmlRenderer } from './renderers/ReactMjmlRenderer'
|
|
23
|
-
export type { Renderer, RenderResult } from './renderers/Renderer'
|
|
24
|
-
export { TemplateRenderer } from './renderers/TemplateRenderer'
|
|
25
|
-
export { VueMjmlRenderer } from './renderers/VueMjmlRenderer'
|
|
26
|
-
export { TypedMailable } from './TypedMailable'
|
|
27
|
-
export { BaseTransport, type TransportOptions } from './transports/BaseTransport'
|
|
28
|
-
export { LogTransport } from './transports/LogTransport'
|
|
29
|
-
export { MemoryTransport } from './transports/MemoryTransport'
|
|
30
|
-
export { SesTransport } from './transports/SesTransport'
|
|
31
|
-
export { SmtpTransport } from './transports/SmtpTransport'
|
|
32
|
-
export type { Transport } from './transports/Transport'
|
|
33
|
-
export type {
|
|
34
|
-
Address,
|
|
35
|
-
Attachment,
|
|
36
|
-
Envelope,
|
|
37
|
-
MailConfig,
|
|
38
|
-
Message,
|
|
39
|
-
} from './types'
|
|
40
|
-
export { type SendGridWebhookConfig, SendGridWebhookDriver } from './webhooks/SendGridWebhookDriver'
|
|
41
|
-
export { SesWebhookDriver } from './webhooks/SesWebhookDriver'
|