PageSourceSearch

https://lnemail.net/static/js/improved/api.js

js lnemail.net collected 2026-09-25 22:15:10 UTC 7,318 bytes, 235 lines download raw bytes

1import { BASE_URL } from './config.js';
2import { state } from './state.js';
3
4async function makeRequest(endpoint, options = {}) {
5    const url = `${BASE_URL}${endpoint}`;
6    const config = {
7        headers: {
8            'Content-Type': 'application/json',
9            ...options.headers
10        },
11        ...options
12    };
13
14    if (state.accessToken) {
15        config.headers['Authorization'] = `Bearer ${state.accessToken}`;
16    }
17
18    try {
19        const response = await fetch(url, config);
20
21        if (!response.ok) {
22            let detail = response.statusText;
23            try {
24                const errorBody = await response.json();
25                detail = errorBody.detail || detail;
26            } catch (_) {
27                // Keep the HTTP status text when the response is not JSON.
28            }
29            throw new Error(detail || `HTTP ${response.status}`);
30        }
31
32        const contentType = response.headers.get('content-type');
33        if (contentType && contentType.includes('application/json')) {
34            return await response.json();
35        } else {
36            return await response.text();
37        }
38    } catch (error) {
39        if (error.name === 'TypeError' && error.message.includes('fetch')) {
40            throw new Error('Network error: Please make sure you\'re running this app through a web server (not file://) to avoid CORS issues. Try running: python -m http.server 8000');
41        }
42
43        throw error;
44    }
45}
46
47export async function checkAccountAuthorization() {
48    try {
49        const response = await makeRequest('/account');
50
51        if (!response || typeof response !== 'object' || !response.email_address || !response.expires_at) {
52            return false;
53        }
54
55        const expiresAt = new Date(response.expires_at);
56        if (isNaN(expiresAt.getTime())) {
57            return false;
58        }
59
60        const isExpired = response.is_expired === true;
61        const daysUntilExpiry = response.days_until_expiry || 0;
62        const renewalEligible = response.renewal_eligible !== false;
63
64        // If expired and not eligible for renewal, reject
65        if (isExpired && !renewalEligible) {
66            return false;
67        }
68
69        state.accountInfo = {
70            email_address: response.email_address,
71            expires_at: response.expires_at,
72            expires_date: expiresAt,
73            is_expired: isExpired,
74            days_until_expiry: daysUntilExpiry,
75            renewal_eligible: renewalEligible,
76            free_send_available: response.free_send_available === true
77        };
78
79        return true;
80    } catch (error) {
81        return false;
82    }
83}
84
85export async function fetchEmails() {
86    const response = await makeRequest('/emails');
87    return response.emails || [];
88}
89
90export async function fetchEmailContent(emailId) {
91    return await makeRequest(`/emails/${emailId}`);
92}
93
94export async function markAllEmailsRead() {
95    return await makeRequest('/emails/read-all', { method: 'POST' });
96}
97
98export async function fetchPasskeys() {
99    return await makeRequest('/passkeys');
100}
101
102export async function beginPasskeyRegistration() {
103    return await makeRequest('/passkeys/registration/options', { method: 'POST' });
104}
105
106export async function finishPasskeyRegistration(ceremonyId, credential, name = 'Passkey') {
107    return await makeRequest('/passkeys/registration', {
108        method: 'POST',
109        body: JSON.stringify({ ceremony_id: ceremonyId, credential, name })
110    });
111}
112
113export async function beginPasskeyAuthentication() {
114    return await makeRequest('/passkeys/authentication/options', { method: 'POST' });
115}
116
117export async function finishPasskeyAuthentication(ceremonyId, credential) {
118    return await makeRequest('/passkeys/authentication', {
119        method: 'POST',
120        body: JSON.stringify({ ceremony_id: ceremonyId, credential })
121    });
122}
123
124export async function removePasskey(credentialId) {
125    return await makeRequest(`/passkeys/${encodeURIComponent(credentialId)}`, {
126        method: 'DELETE'
127    });
128}
129
130export async function sendEmail(recipient, subject, body, inReplyTo = null, references = null, attachments = []) {
131    const payload = { recipient, subject, body };
132    if (inReplyTo) payload.in_reply_to = inReplyTo;
133    if (references) payload.references = references;
134    if (attachments.length > 0) payload.attachments = attachments;
135
136    return await makeRequest('/email/send', {
137        method: 'POST',
138        body: JSON.stringify(payload)
139    });
140}
141
142export async function checkPaymentStatus(paymentHash) {
143    return await makeRequest(`/email/send/status/${paymentHash}`, {
144        method: 'GET'
145    });
146}
147
148export async function checkApiHealth() {
149    try {
150        const controller = new AbortController();
151        const timeoutId = setTimeout(() => controller.abort(), 10000);
152        const response = await makeRequest('/health', { signal: controller.signal });
153        clearTimeout(timeoutId);
154        return {
155            success: true,
156            data: response
157        };
158    } catch (error) {
159        return {
160            success: false,
161            error: error.name === 'AbortError' ? 'Health check timed out after 10 seconds' : error.message
162        };
163    }
164}
165
166export async function deleteEmails(emailIds) {
167    try {
168        const response = await makeRequest('/emails', {
169            method: 'DELETE',
170            body: JSON.stringify({ email_ids: emailIds })
171        });
172        return {
173            success: true,
174            data: response
175        };
176    } catch (error) {
177        return {
178            success: false,
179            error: error.message
180        };
181    }
182}
183
184export async function createEmailAccount(options = {}) {
185    return await makeRequest('/email', {
186        method: 'POST',
187        body: JSON.stringify(options)
188    });
189}
190
191export async function checkAccountPaymentStatus(paymentHash) {
192    return await makeRequest(`/payment/${paymentHash}`, {
193        method: 'GET'
194    });
195}
196
197export async function fetchRecentSends() {
198    return await makeRequest('/email/sends/recent');
199}
200
201export async function renewAccount(years = 1) {
202    return await makeRequest('/account/renew', {
203        method: 'POST',
204        body: JSON.stringify({ years })
205    });
206}
207
208export async function checkRenewalStatus(paymentHash) {
209    return await makeRequest(`/account/renew/status/${paymentHash}`, {
210        method: 'GET'
211    });
212}
213
214// Re-issue an invoice from a different payment provider (when configured).
215// excludeProvider is the provider that issued the current, unpayable invoice.
216export async function newAccountInvoice(paymentHash, excludeProvider = null) {
217    return await makeRequest(`/email/${paymentHash}/new-invoice`, {
218        method: 'POST',
219        body: JSON.stringify({ exclude_provider: excludeProvider })
220    });
221}
222
223export async function newSendInvoice(paymentHash, excludeProvider = null) {
224    return await makeRequest(`/email/send/${paymentHash}/new-invoice`, {
225        method: 'POST',
226        body: JSON.stringify({ exclude_provider: excludeProvider })
227    });
228}
229
230export async function newRenewalInvoice(paymentHash, excludeProvider = null, years = 1) {
231    return await makeRequest(`/account/renew/${paymentHash}/new-invoice`, {
232        method: 'POST',
233        body: JSON.stringify({ exclude_provider: excludeProvider, years })
234    });
235}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.