/** MailDock v1, Node.js 20+; no dependencies. Importing causes no requests. * Set MAILDOCK_API_KEY; running this file only reads balance. * Persist create parameters and the idempotency key before sending a POST. */ import { pathToFileURL } from 'node:url'; import { setTimeout as sleep } from 'node:timers/promises'; export class APIError extends Error { constructor(status, code, message, retryAfter = 60) { super(`HTTP ${status} ${code}: ${message}`); Object.assign(this, { status, code, retryAfter }); } } export class MailDock { constructor({ apiKey = process.env.MAILDOCK_API_KEY, baseUrl = process.env.MAILDOCK_BASE_URL || 'https://mail.jqiudata.com', timeout = 30000 } = {}) { const url = new URL(baseUrl); const localHttp = url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname); if ((url.protocol !== 'https:' && !localHttp) || url.username || url.password || url.search || url.hash || url.pathname !== '/') { throw new Error('baseUrl must be an HTTPS origin (HTTP allowed only on loopback).'); } if (!apiKey) throw new Error('Set MAILDOCK_API_KEY in your environment.'); if (!(timeout > 0)) throw new Error('timeout must be positive.'); Object.assign(this, { apiKey, baseUrl: url.origin, timeout }); } async request(method, path, body, idempotencyKey, timeout = this.timeout) { const headers = { Authorization: `Bearer ${this.apiKey}`, Accept: 'application/json' }; if (body !== undefined) headers['Content-Type'] = 'application/json'; if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey; let response, payload; try { response = await fetch(this.baseUrl + path, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), redirect: 'error', signal: AbortSignal.timeout(Math.max(1, Math.ceil(timeout))) }); payload = await response.json(); } catch (cause) { throw new Error(`${method}: network or non-JSON response; a write may have committed. Preserve the order ID; replay create only with the original key and parameters.`, { cause }); } if (!response.ok) { const parsedDelay = Number(response.headers.get('Retry-After') ?? 60); const retryAfter = Number.isFinite(parsedDelay) ? Math.max(1, parsedDelay) : 60; throw new APIError(response.status, payload?.error?.code || 'http_error', payload?.error?.message || 'Request failed; preserve the original request.', retryAfter); } if (!payload || typeof payload !== 'object' || Array.isArray(payload)) throw new Error('Unexpected response shape; preserve the original request.'); return payload; } orderPath(id) { if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) throw new Error('Expected order UUID.'); return `/v1/mail-orders/${id}`; } balance() { return this.request('GET', '/v1/balance'); } products({ search = '', domain = '', page = 1 } = {}) { return this.request('GET', '/v1/products?' + new URLSearchParams({ search, domain, page })); } orders(page = 1) { return this.request('GET', '/v1/mail-orders?' + new URLSearchParams({ page })); } transactions(page = 1) { return this.request('GET', '/v1/transactions?' + new URLSearchParams({ page })); } create(productId, maxPrice, idempotencyKey) { if (typeof maxPrice !== 'string' || !/^\d+(\.\d{1,8})?$/.test(maxPrice)) throw new Error('maxPrice must be a decimal string with at most 8 decimal places.'); if (typeof idempotencyKey !== 'string' || !/^[\x21-\x7e]{8,100}$/.test(idempotencyKey)) throw new Error('Supply a persisted 8–100 character printable ASCII key without spaces.'); this.orderPath(productId); // Validate product UUID using the same syntax. return this.request('POST', '/v1/mail-orders', { product_id: productId, max_price: maxPrice }, idempotencyKey); } order(id) { return this.request('GET', this.orderPath(id)); } cancel(id) { return this.request('POST', this.orderPath(id) + '/cancel'); } async waitForCode(id, { timeout = 600, interval = 5 } = {}) { // Call after the email has been allocated and the target email triggered. if (!(timeout > 0) || !(interval >= 5)) throw new Error('timeout must be positive; interval must be at least 5 seconds.'); const deadline = performance.now() + timeout * 1000; while (performance.now() < deadline) { let delay = interval; try { const { order } = await this.request('GET', this.orderPath(id), undefined, undefined, Math.min(this.timeout, Math.max(1, deadline - performance.now()))); if (order.code !== null && order.code !== undefined) return order; if (['review', 'failed', 'canceled', 'completed'].includes(order.state)) { throw new Error(`Order ${id}: ${order.state} without code; inspect this order, do not automatically buy again.`); } } catch (error) { if (!(error instanceof APIError) || error.status !== 429 || error.code !== 'rate_limited') throw error; delay = Math.max(interval, error.retryAfter); } const remaining = deadline - performance.now(); if (remaining > 0) await sleep(Math.min(delay * 1000, remaining)); } throw new Error(`Order ${id}: local wait expired; order was not canceled. Keep the ID and query or cancel explicitly.`); } } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { try { console.log(JSON.stringify(await new MailDock().balance(), null, 2)); } catch (error) { console.error(error.message); process.exitCode = 1; } }