@ahrara/sdk 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/LICENSE-THIRD-PARTY.md +7916 -0
- package/README.md +155 -2
- package/index.d.ts +235 -0
- package/index.js +541 -0
- package/native/darwin-arm64/libahrara_native.dylib +0 -0
- package/native/darwin-x64/libahrara_native.dylib +0 -0
- package/native/linux-arm64/libahrara_native.so +0 -0
- package/native/linux-x64/libahrara_native.so +0 -0
- package/native/win32-arm64/ahrara_native.dll +0 -0
- package/native/win32-x64/ahrara_native.dll +0 -0
- package/package.json +58 -4
package/README.md
CHANGED
|
@@ -1,3 +1,156 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Ahrara for JavaScript and TypeScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Create an email address from Node.js, wait for messages, and extract verification
|
|
4
|
+
codes or links in JavaScript or TypeScript. Requires Node.js 20+; install with
|
|
5
|
+
`npm install @ahrara/sdk`. TypeScript declarations are included. The SDK connects
|
|
6
|
+
directly to the relay and runs in the application or test runner, not in the
|
|
7
|
+
browser.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { Ahrara } from '@ahrara/sdk';
|
|
11
|
+
|
|
12
|
+
const inbox = await Ahrara.createInbox();
|
|
13
|
+
try {
|
|
14
|
+
// Submit inbox.address in your application.
|
|
15
|
+
const email = await inbox.waitForEmail(); // No implicit timeout.
|
|
16
|
+
// Assert on email.subject / email.text_body.
|
|
17
|
+
} finally {
|
|
18
|
+
await inbox.close();
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Creation returns after authentication and subscription. Mail arriving before a
|
|
23
|
+
wait is buffered. Each matching receive is consumed once, while storage retains
|
|
24
|
+
the message until deleted. Create separate inboxes for parallel tests. Concurrent
|
|
25
|
+
waits on one inbox are queued and run one at a time, in call order.
|
|
26
|
+
|
|
27
|
+
## Waits, streams, and callbacks
|
|
28
|
+
|
|
29
|
+
`waitForEmail({ timeoutMs, subject, from, signal })` waits indefinitely when
|
|
30
|
+
`timeoutMs` is omitted. An explicit positive integer deadline is in milliseconds
|
|
31
|
+
and throws `AhraraError` with code `timeout`. `subject` and `from` are ASCII
|
|
32
|
+
case-insensitive substrings. AbortSignal cancellation and `close()` interrupt
|
|
33
|
+
waits; cancelling a wait preserves the inbox. A simultaneously matched email may
|
|
34
|
+
win the cancellation race. Native work runs off the Node event loop.
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
for await (const email of inbox.waitForEmails({ timeoutMs: 10_000 })) {
|
|
38
|
+
await verify(email);
|
|
39
|
+
if (finished()) break;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await inbox.receiveEmails(async email => {
|
|
43
|
+
await verify(email);
|
|
44
|
+
}, { timeoutMs: 10_000 });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A stream's finite timeout is its **total duration**, including consumer/callback
|
|
48
|
+
work, and expiry ends normally. Omit timeout to keep receiving indefinitely.
|
|
49
|
+
Callbacks are awaited sequentially; errors propagate. Breaking a stream,
|
|
50
|
+
returning its iterator, cancellation, or callback failure leaves the inbox open.
|
|
51
|
+
There is no prefetch or hidden message queue. Use an AbortSignal to interrupt an
|
|
52
|
+
active `next()`; awaiting `return()` alone cannot interrupt an in-flight receive.
|
|
53
|
+
The application remains responsible for closing the inbox.
|
|
54
|
+
|
|
55
|
+
## Filters, regex captures, and XPath
|
|
56
|
+
|
|
57
|
+
Pass `filter` to `createInbox()` to discard nonmatching incoming email before
|
|
58
|
+
storage, or to a wait/search method to leave nonmatching email available for
|
|
59
|
+
later waits. All filter fields are ANDed. `sender`, `subject`, `to`, `cc`, `text`,
|
|
60
|
+
and `headers: { name: value }` use case-insensitive substring matching.
|
|
61
|
+
`recipient`, `envelopeSender`, and `messageId` match exactly, ignoring case;
|
|
62
|
+
`hasAttachments` selects presence or absence of attachments. Header names ignore
|
|
63
|
+
case. A per-wait filter adds to the inbox's receipt filter.
|
|
64
|
+
|
|
65
|
+
Set `filter: { regex: true, subject: '^(Verification|Confirm email)$' }` to
|
|
66
|
+
interpret all string conditions as case-insensitive Rust regex patterns; regex
|
|
67
|
+
queries below are case-sensitive unless `ignoreCase` is set. The top-level
|
|
68
|
+
`subject`/`from` wait options stay literal. See the
|
|
69
|
+
[shared filter rules](https://github.com/Hitmasu/Ahrara/blob/main/docs/sdks.md#filters-and-extraction)
|
|
70
|
+
for anchoring and limits.
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
const match = await inbox.waitForRegexMatch('code=(?<code>[0-9]+)', {
|
|
74
|
+
filter: { sender: 'example.test', subject: 'Verify' },
|
|
75
|
+
ignoreCase: true,
|
|
76
|
+
});
|
|
77
|
+
const code = match.groups.find(group => group.name === 'code').value;
|
|
78
|
+
|
|
79
|
+
for await (const match of inbox.waitForRegexMatches('[0-9]+', { timeoutMs: 10_000 })) {
|
|
80
|
+
await verifyCode(match.value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const link = await inbox.waitForXPath('//a[@id="verify"]/@href');
|
|
84
|
+
for await (const value of inbox.waitForXPathValues('//a/@href', { timeoutMs: 10_000 })) {
|
|
85
|
+
await verifyLink(value);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Regex uses the shared [Rust regex syntax](https://docs.rs/regex/latest/regex/#syntax),
|
|
90
|
+
including named groups; lookaround and backreferences are unsupported. Options
|
|
91
|
+
are `source: 'text' | 'html' | 'subject' | 'raw'` (default full decoded text),
|
|
92
|
+
`ignoreCase`, `multiLine`, and `dotMatchesNewLine`. A result contains `email_id`,
|
|
93
|
+
`value`, and indexed `groups`; group zero is the whole match and unmatched optional
|
|
94
|
+
groups have a null value. HTML searches use original decoded HTML.
|
|
95
|
+
|
|
96
|
+
Singular calls return the first value/match and throw on timeout. Plural methods
|
|
97
|
+
yield **every match/value across multiple emails**, with one total timeout that
|
|
98
|
+
includes consumer work. Finite expiry ends normally; omitted timeout is indefinite.
|
|
99
|
+
`signal` cancels either form. Invalid expressions and filters throw before
|
|
100
|
+
consuming email. Each matched email is consumed once: stopping partway through
|
|
101
|
+
its results does not replay the remaining values, but its stored message remains
|
|
102
|
+
readable.
|
|
103
|
+
|
|
104
|
+
## Persistence and identity
|
|
105
|
+
|
|
106
|
+
`createInbox({ profilePath: '/absolute/profile/directory' })` retains identity,
|
|
107
|
+
address, encrypted history, and consumed-receive state across close/reopen. Omit
|
|
108
|
+
`profilePath` for a disposable temporary profile. The same active profile cannot
|
|
109
|
+
be opened twice. Explicitly clear/delete messages to remove retained history.
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
const credentials = await inbox.exportCredentials();
|
|
113
|
+
await inbox.close();
|
|
114
|
+
const restored = await Ahrara.createInbox({ credentials });
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Credentials restore the identity/address, with fresh local history unless a
|
|
118
|
+
persistent profile is supplied. Inspection and JSON serialization redact the
|
|
119
|
+
Recovery Key; access `credentials.recoveryKey` only for deliberate secure storage.
|
|
120
|
+
Never log it. Alternatively pass `recoveryKey` and optional owned `address`;
|
|
121
|
+
these cannot be combined with `credentials`. An `address` alone requires an
|
|
122
|
+
existing profile identity. Unowned addresses and mismatched profile keys fail.
|
|
123
|
+
|
|
124
|
+
Connection options are `serverUrl`, `serverPublicKey`, `tlsCaPath`, and
|
|
125
|
+
`connectTimeoutMs` (1..300000 ms, default 20000). Omit them for the hosted relay,
|
|
126
|
+
whose verified public key the package embeds; a private relay's public key must be
|
|
127
|
+
independently verified. Custom CAs keep TLS verification enabled.
|
|
128
|
+
|
|
129
|
+
## Stored email and attachments
|
|
130
|
+
|
|
131
|
+
All operations are scoped to the inbox's address. Listing/getting does not
|
|
132
|
+
consume receive state. Native errors retain their code in `AhraraError.code`.
|
|
133
|
+
|
|
134
|
+
| Method | Result |
|
|
135
|
+
| --- | --- |
|
|
136
|
+
| `listEmails({ afterId, limit, subject, from })` | `{ emails, next_cursor }` metadata page; limit 1..100 |
|
|
137
|
+
| `getEmail(id)` | Full parsed email with attachment metadata |
|
|
138
|
+
| `deleteEmail(id)` / `clearEmails()` | Number of messages removed |
|
|
139
|
+
| `getAttachment(id, attachmentId)` | `{ attachment, content: Buffer }` |
|
|
140
|
+
| `saveAttachment(id, attachmentId, absolutePath)` | Saved file path; never overwrites; directories use a sanitized filename |
|
|
141
|
+
| `getRawEmail(id)` | Original RFC 822 bytes as Buffer |
|
|
142
|
+
| `getHtml(id, { allowRemote: false })` | Sanitized `{ html, remote_resources }`; remote resources blocked by default |
|
|
143
|
+
|
|
144
|
+
Email fields use the native snake_case schema, including `from`, `to`, `subject`,
|
|
145
|
+
`text_body`, `attachments`, and `parse_error`. Extracted text is capped at 256 KiB
|
|
146
|
+
and reports `text_body_truncated`. HTML and attachments are fetched explicitly.
|
|
147
|
+
Treat message content as untrusted data.
|
|
148
|
+
|
|
149
|
+
## Test frameworks
|
|
150
|
+
|
|
151
|
+
Playwright uses a test fixture owning an inbox; see
|
|
152
|
+
[`signup.spec.js`](https://github.com/Hitmasu/Ahrara/blob/main/src/sdks/javascript/test/signup.spec.js).
|
|
153
|
+
Cypress uses Node `setupNodeEvents` and `cy.task`, since browser code cannot load
|
|
154
|
+
the native library; see
|
|
155
|
+
[`cypress.config.js`](https://github.com/Hitmasu/Ahrara/blob/main/src/sdks/javascript/cypress.config.js).
|
|
156
|
+
Node/Jest/Vitest use the same API.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/** An address and its sensitive Recovery Key; never log the key. */
|
|
2
|
+
export interface CredentialsInput {
|
|
3
|
+
address: string;
|
|
4
|
+
recoveryKey: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Exported credentials redact inspection and JSON; access recoveryKey explicitly. */
|
|
8
|
+
export class InboxCredentials implements CredentialsInput {
|
|
9
|
+
constructor(address: string, recoveryKey: string);
|
|
10
|
+
readonly address: string;
|
|
11
|
+
readonly recoveryKey: string;
|
|
12
|
+
toJSON(): { address: string; recoveryKey: string };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Connection, local persistence, and optional identity restoration. */
|
|
16
|
+
export interface InboxOptions {
|
|
17
|
+
/** Receipt filter applied before storage. Rejected emails are dropped. */
|
|
18
|
+
filter?: EmailFilter;
|
|
19
|
+
/** wss:// relay URL; defaults to the release core's production endpoint. */
|
|
20
|
+
serverUrl?: string;
|
|
21
|
+
/** Independently verified public Noise key, not a Recovery Key. */
|
|
22
|
+
serverPublicKey?: string;
|
|
23
|
+
/** Optional custom trusted CA PEM file. TLS verification remains enabled. */
|
|
24
|
+
tlsCaPath?: string;
|
|
25
|
+
/** Connection deadline in milliseconds, 1..300000; core default 20000. */
|
|
26
|
+
connectTimeoutMs?: number;
|
|
27
|
+
/** Absolute persistent profile directory. Omit for an isolated temporary profile. */
|
|
28
|
+
profilePath?: string;
|
|
29
|
+
/** Restore exported credentials; mutually exclusive with recoveryKey/address. */
|
|
30
|
+
credentials?: CredentialsInput;
|
|
31
|
+
/** Sensitive identity import, optionally paired with an owned address. */
|
|
32
|
+
recoveryKey?: string;
|
|
33
|
+
/** Owned address to select; alone requires an existing profile identity. */
|
|
34
|
+
address?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface WaitOptions {
|
|
38
|
+
/** Additional filter; nonmatching stored emails remain available for later waits. */
|
|
39
|
+
filter?: EmailFilter;
|
|
40
|
+
/** Positive integer milliseconds. Omitted means indefinite; streams use a total deadline. */
|
|
41
|
+
timeoutMs?: number;
|
|
42
|
+
/** Substring filter using the core's ASCII case-insensitive matching. */
|
|
43
|
+
subject?: string;
|
|
44
|
+
/** Sender substring filter using the same matching rules. */
|
|
45
|
+
from?: string;
|
|
46
|
+
/** Abort only this wait/subscription. Cancellation leaves the inbox open. */
|
|
47
|
+
signal?: AbortSignal;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Fields are ANDed. Strings match case-insensitive substrings, except recipient,
|
|
52
|
+
* envelopeSender, and messageId, which match the whole value. With `regex: true`,
|
|
53
|
+
* strings are case-insensitive Rust regex patterns: those three are anchored to
|
|
54
|
+
* the whole value; the others match anywhere.
|
|
55
|
+
*/
|
|
56
|
+
export interface EmailFilter {
|
|
57
|
+
/** Interpret string conditions, including header values, as regex. Header names stay literal. */
|
|
58
|
+
regex?: boolean;
|
|
59
|
+
sender?: string;
|
|
60
|
+
subject?: string;
|
|
61
|
+
to?: string;
|
|
62
|
+
cc?: string;
|
|
63
|
+
text?: string;
|
|
64
|
+
/** Whole SMTP recipient, ignoring case; an anchored pattern with `regex`. */
|
|
65
|
+
recipient?: string;
|
|
66
|
+
/** Whole SMTP envelope sender, ignoring case; an anchored pattern with `regex`. */
|
|
67
|
+
envelopeSender?: string;
|
|
68
|
+
/** Whole Message-ID header, ignoring case; an anchored pattern with `regex`. */
|
|
69
|
+
messageId?: string;
|
|
70
|
+
hasAttachments?: boolean;
|
|
71
|
+
/** Literal, case-insensitive header names; each value matches anywhere in a header value. */
|
|
72
|
+
headers?: Record<string, string>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Shared search lifecycle. Single waits throw on timeout; streams end normally. */
|
|
76
|
+
export interface SearchOptions {
|
|
77
|
+
/** Positive integer milliseconds, or omitted for indefinite. Streams use total duration. */
|
|
78
|
+
timeoutMs?: number;
|
|
79
|
+
/** Abort this search/stream while keeping the inbox open. */
|
|
80
|
+
signal?: AbortSignal;
|
|
81
|
+
/** Nonmatching stored emails remain unconsumed. */
|
|
82
|
+
filter?: EmailFilter;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface RegexOptions extends SearchOptions {
|
|
86
|
+
/** Search full decoded text (default), original HTML, subject, or raw RFC 822 text. */
|
|
87
|
+
source?: "text" | "html" | "subject" | "raw";
|
|
88
|
+
/** Defaults to false: query regex is case-sensitive, unlike filter regex. */
|
|
89
|
+
ignoreCase?: boolean;
|
|
90
|
+
multiLine?: boolean;
|
|
91
|
+
dotMatchesNewLine?: boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface RegexGroup {
|
|
95
|
+
/** Zero identifies the whole match. Remaining indices follow capture order. */
|
|
96
|
+
index: number;
|
|
97
|
+
/** Named capture identifier, or null for unnamed groups. */
|
|
98
|
+
name?: string | null;
|
|
99
|
+
/** Null when an optional group did not participate in this match. */
|
|
100
|
+
value?: string | null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** A matching substring and all its captures, associated with an inbox-owned email. */
|
|
104
|
+
export interface RegexMatch {
|
|
105
|
+
email_id: number;
|
|
106
|
+
value: string;
|
|
107
|
+
groups: RegexGroup[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface ListOptions {
|
|
111
|
+
/** Exclusive message-ID cursor; defaults to 0. */
|
|
112
|
+
afterId?: number;
|
|
113
|
+
/** Page size 1..100; defaults to 20. */
|
|
114
|
+
limit?: number;
|
|
115
|
+
subject?: string;
|
|
116
|
+
from?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Stored metadata. Field names match the shared native email schema. */
|
|
120
|
+
export interface EmailMetadata {
|
|
121
|
+
id: number;
|
|
122
|
+
transfer_id: string;
|
|
123
|
+
identity_fingerprint: string;
|
|
124
|
+
recipient: string;
|
|
125
|
+
envelope_sender: string;
|
|
126
|
+
received_at: string;
|
|
127
|
+
from: string;
|
|
128
|
+
to: string;
|
|
129
|
+
subject: string;
|
|
130
|
+
message_id: string | null;
|
|
131
|
+
size_bytes: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface Attachment {
|
|
135
|
+
attachment_id: string;
|
|
136
|
+
filename: string;
|
|
137
|
+
content_type: string;
|
|
138
|
+
size: number;
|
|
139
|
+
content_disposition: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface Email extends EmailMetadata {
|
|
143
|
+
cc: string;
|
|
144
|
+
date: string;
|
|
145
|
+
forwarded_to: string | null;
|
|
146
|
+
/** Extracted text, capped by the core at 256 KiB. */
|
|
147
|
+
text_body: string;
|
|
148
|
+
text_body_truncated: boolean;
|
|
149
|
+
html_available: boolean;
|
|
150
|
+
attachments: Attachment[];
|
|
151
|
+
parse_error: boolean;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface EmailPage {
|
|
155
|
+
emails: EmailMetadata[];
|
|
156
|
+
next_cursor: number | null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface AttachmentData {
|
|
160
|
+
attachment: Attachment;
|
|
161
|
+
content: Buffer;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface HtmlEmail {
|
|
165
|
+
html: string;
|
|
166
|
+
remote_resources: number;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Native errors retain a stable code, including timeout/inbox_closed. */
|
|
170
|
+
export class AhraraError extends Error {
|
|
171
|
+
constructor(code: string, message: string);
|
|
172
|
+
readonly code: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** A ready, caller-owned inbox. Close explicitly or use await using. */
|
|
176
|
+
export class Inbox {
|
|
177
|
+
private constructor();
|
|
178
|
+
readonly address: string;
|
|
179
|
+
/** Consume one matching email. Concurrent waits queue. Omitted timeout waits indefinitely. Throws on finite timeout. */
|
|
180
|
+
waitForEmail(options?: WaitOptions): Promise<Email>;
|
|
181
|
+
/** Sequential stream; total timeout includes consumer work and expires normally. */
|
|
182
|
+
waitForEmails(options?: WaitOptions): AsyncGenerator<Email, void, unknown>;
|
|
183
|
+
/** Await each callback; propagate callback errors. No prefetch or hidden background queue. */
|
|
184
|
+
receiveEmails(
|
|
185
|
+
handler: (email: Email) => void | Promise<void>,
|
|
186
|
+
options?: WaitOptions,
|
|
187
|
+
): Promise<void>;
|
|
188
|
+
/** First regex match with captures. Invalid queries never consume email. */
|
|
189
|
+
waitForRegexMatch(
|
|
190
|
+
pattern: string,
|
|
191
|
+
options?: RegexOptions,
|
|
192
|
+
): Promise<RegexMatch>;
|
|
193
|
+
/** Every match across emails; total deadline includes consumer work. Expiry ends normally. */
|
|
194
|
+
waitForRegexMatches(
|
|
195
|
+
pattern: string,
|
|
196
|
+
options?: RegexOptions,
|
|
197
|
+
): AsyncGenerator<RegexMatch, void, unknown>;
|
|
198
|
+
/** First XPath string value from a matching email's HTML. */
|
|
199
|
+
waitForXPath(expression: string, options?: SearchOptions): Promise<string>;
|
|
200
|
+
/** Every XPath value across emails; same lifetime and deadline rules as regex streams. */
|
|
201
|
+
waitForXPathValues(
|
|
202
|
+
expression: string,
|
|
203
|
+
options?: SearchOptions,
|
|
204
|
+
): AsyncGenerator<string, void, unknown>;
|
|
205
|
+
/** Export identity credentials explicitly; their Recovery Key is sensitive. */
|
|
206
|
+
exportCredentials(): Promise<InboxCredentials>;
|
|
207
|
+
/** Read metadata without consuming the receive cursor. */
|
|
208
|
+
listEmails(options?: ListOptions): Promise<EmailPage>;
|
|
209
|
+
/** Read full parsed email; IDs are scoped to this inbox's address. */
|
|
210
|
+
getEmail(id: number): Promise<Email>;
|
|
211
|
+
/** Delete one stored message and return the number removed. */
|
|
212
|
+
deleteEmail(id: number): Promise<number>;
|
|
213
|
+
/** Clear only this inbox's messages and return the number removed. */
|
|
214
|
+
clearEmails(): Promise<number>;
|
|
215
|
+
/** Return attachment metadata and bytes without touching the filesystem. */
|
|
216
|
+
getAttachment(id: number, attachmentId: string): Promise<AttachmentData>;
|
|
217
|
+
/** Save to an absolute file/directory path; never overwrite. Returns the saved path. */
|
|
218
|
+
saveAttachment(
|
|
219
|
+
id: number,
|
|
220
|
+
attachmentId: string,
|
|
221
|
+
destination: string,
|
|
222
|
+
): Promise<string>;
|
|
223
|
+
/** Original RFC 822 message bytes. */
|
|
224
|
+
getRawEmail(id: number): Promise<Buffer>;
|
|
225
|
+
/** Sanitized HTML. Remote resources are blocked by default. */
|
|
226
|
+
getHtml(id: number, options?: { allowRemote?: boolean }): Promise<HtmlEmail>;
|
|
227
|
+
/** Idempotent shutdown. Interrupts waits; persistent profile files remain. */
|
|
228
|
+
close(): Promise<void>;
|
|
229
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Return after authentication/subscription, or throw a native configuration/connection error. */
|
|
233
|
+
export function createInbox(options?: InboxOptions): Promise<Inbox>;
|
|
234
|
+
|
|
235
|
+
export const Ahrara: Readonly<{ createInbox: typeof createInbox }>;
|