@rozoai/checkout 0.1.17 → 0.1.18
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 +15 -2
- package/llms.txt +5 -2
- package/package.json +1 -1
- package/scripts/dist/cli.js +152 -40
- package/scripts/dist/create-order.js +105 -34
- package/scripts/dist/quote.js +99 -33
- package/scripts/dist/receipt.js +99 -33
- package/scripts/dist/resume.js +99 -33
- package/scripts/dist/send-evm.js +99 -33
- package/scripts/dist/send-sol.js +99 -33
- package/scripts/dist/status.js +118 -35
package/scripts/dist/quote.js
CHANGED
|
@@ -13,6 +13,8 @@ var SUPPORT = Object.freeze({
|
|
|
13
13
|
discord: "https://discord.gg/EfWejgTbuU"
|
|
14
14
|
});
|
|
15
15
|
var SUPPORT_TEXT = `Need help? Email ${SUPPORT.email}, or reach ROZO on X ${SUPPORT.x} or Discord ${SUPPORT.discord}.`;
|
|
16
|
+
var BULK_DOCS_URL = "https://docs.rozo.ai/products/checkout/bulk-and-agents";
|
|
17
|
+
var BULK_HINT_TEXT = `Buying regularly or for others? ${BULK_DOCS_URL}`;
|
|
16
18
|
|
|
17
19
|
// scripts/src/lib/output.mjs
|
|
18
20
|
var PUBLIC_SUPPORT_URLS = /* @__PURE__ */ new Set([SUPPORT.x, SUPPORT.discord]);
|
|
@@ -152,18 +154,66 @@ function extractLinkId(urlOrId) {
|
|
|
152
154
|
);
|
|
153
155
|
}
|
|
154
156
|
|
|
155
|
-
// scripts/src/lib/
|
|
157
|
+
// scripts/src/lib/version.mjs
|
|
156
158
|
import { createRequire } from "node:module";
|
|
159
|
+
var PKG_VERSION = (() => {
|
|
160
|
+
const requireFrom = createRequire(import.meta.url);
|
|
161
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
162
|
+
try {
|
|
163
|
+
const pkg = requireFrom(candidate);
|
|
164
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return "0.0.0";
|
|
169
|
+
})();
|
|
157
170
|
|
|
158
171
|
// scripts/src/lib/http.mjs
|
|
159
172
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
160
|
-
var USER_AGENT =
|
|
161
|
-
|
|
173
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
174
|
+
var MAX_RETRIES = 3;
|
|
175
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
176
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
177
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
178
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
179
|
+
if (value === null || value === void 0) return null;
|
|
180
|
+
const s = String(value).trim();
|
|
181
|
+
if (!s) return null;
|
|
182
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
183
|
+
const at = Date.parse(s);
|
|
184
|
+
if (!Number.isFinite(at)) return null;
|
|
185
|
+
return Math.max(0, at - now);
|
|
186
|
+
}
|
|
187
|
+
function bodyRetryAfterMs(json) {
|
|
188
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
189
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
190
|
+
const n = Number(raw);
|
|
191
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
192
|
+
}
|
|
193
|
+
function rateLimitInfo(res) {
|
|
194
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
195
|
+
const info = {
|
|
196
|
+
limit: pick("x-ratelimit-limit"),
|
|
197
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
198
|
+
tier: pick("x-ratelimit-tier"),
|
|
199
|
+
scope: pick("x-ratelimit-scope")
|
|
200
|
+
};
|
|
201
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
202
|
+
return Object.keys(present).length ? present : null;
|
|
203
|
+
}
|
|
204
|
+
function parseBody(text) {
|
|
205
|
+
if (!text) return null;
|
|
206
|
+
try {
|
|
207
|
+
return JSON.parse(text);
|
|
208
|
+
} catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
162
213
|
const controller = new AbortController();
|
|
163
214
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
164
|
-
let res;
|
|
165
215
|
try {
|
|
166
|
-
res = await fetch(url, {
|
|
216
|
+
const res = await fetch(url, {
|
|
167
217
|
method,
|
|
168
218
|
headers: {
|
|
169
219
|
accept: "application/json",
|
|
@@ -173,6 +223,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
173
223
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
174
224
|
signal: controller.signal
|
|
175
225
|
});
|
|
226
|
+
const text = await res.text();
|
|
227
|
+
return { res, text };
|
|
176
228
|
} catch (err) {
|
|
177
229
|
throw new SkillError(
|
|
178
230
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -182,31 +234,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
182
234
|
} finally {
|
|
183
235
|
clearTimeout(timer);
|
|
184
236
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
237
|
+
}
|
|
238
|
+
async function request(method, url, {
|
|
239
|
+
body,
|
|
240
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
241
|
+
sleep = defaultSleep,
|
|
242
|
+
now = Date.now,
|
|
243
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
244
|
+
} = {}) {
|
|
245
|
+
let waitedMs = 0;
|
|
246
|
+
let retries = 0;
|
|
247
|
+
for (; ; ) {
|
|
248
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
249
|
+
const json = parseBody(text);
|
|
250
|
+
if (res.ok) {
|
|
251
|
+
if (json === null) {
|
|
252
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
253
|
+
url: redactUrl(url),
|
|
254
|
+
snippet: redact(text).slice(0, 400)
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return json;
|
|
258
|
+
}
|
|
259
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
260
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
261
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
262
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
263
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
264
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
265
|
+
retries += 1;
|
|
266
|
+
waitedMs += waitMs;
|
|
267
|
+
await sleep(waitMs);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
192
270
|
}
|
|
193
|
-
}
|
|
194
|
-
if (!res.ok) {
|
|
195
271
|
const code = json?.code || json?.error?.code || (typeof json?.error === "string" && /^[A-Z][A-Z0-9_]+$/.test(json.error) ? json.error : null) || `HTTP_${res.status}`;
|
|
196
272
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
273
|
+
const rateLimit = rateLimitInfo(res);
|
|
197
274
|
throw new SkillError(code, redact(String(message)), {
|
|
198
275
|
httpStatus: res.status,
|
|
199
276
|
url: redactUrl(url),
|
|
200
|
-
body: json ?? redact(text).slice(0, 800)
|
|
277
|
+
body: json ?? redact(text).slice(0, 800),
|
|
278
|
+
...retryable ? {
|
|
279
|
+
retries,
|
|
280
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
281
|
+
} : {},
|
|
282
|
+
...rateLimit ? { rateLimit } : {}
|
|
201
283
|
});
|
|
202
284
|
}
|
|
203
|
-
if (json === null) {
|
|
204
|
-
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
205
|
-
url: redactUrl(url),
|
|
206
|
-
snippet: redact(text).slice(0, 400)
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
return json;
|
|
210
285
|
}
|
|
211
286
|
function redactUrl(url) {
|
|
212
287
|
try {
|
|
@@ -221,17 +296,6 @@ function postJson(url, body, opts) {
|
|
|
221
296
|
}
|
|
222
297
|
|
|
223
298
|
// scripts/src/lib/api.mjs
|
|
224
|
-
var PKG_VERSION = (() => {
|
|
225
|
-
const requireFrom = createRequire(import.meta.url);
|
|
226
|
-
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
227
|
-
try {
|
|
228
|
-
const pkg = requireFrom(candidate);
|
|
229
|
-
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
230
|
-
} catch {
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return "0.0.0";
|
|
234
|
-
})();
|
|
235
299
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
236
300
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
237
301
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
@@ -300,6 +364,8 @@ var MARGINS_MS = {
|
|
|
300
364
|
};
|
|
301
365
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
302
366
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
367
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
368
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
303
369
|
function parseDeadline(value) {
|
|
304
370
|
if (value === null || value === void 0 || value === "") return null;
|
|
305
371
|
if (typeof value === "number" && Number.isFinite(value)) {
|
package/scripts/dist/receipt.js
CHANGED
|
@@ -13,6 +13,8 @@ var SUPPORT = Object.freeze({
|
|
|
13
13
|
discord: "https://discord.gg/EfWejgTbuU"
|
|
14
14
|
});
|
|
15
15
|
var SUPPORT_TEXT = `Need help? Email ${SUPPORT.email}, or reach ROZO on X ${SUPPORT.x} or Discord ${SUPPORT.discord}.`;
|
|
16
|
+
var BULK_DOCS_URL = "https://docs.rozo.ai/products/checkout/bulk-and-agents";
|
|
17
|
+
var BULK_HINT_TEXT = `Buying regularly or for others? ${BULK_DOCS_URL}`;
|
|
16
18
|
|
|
17
19
|
// scripts/src/lib/output.mjs
|
|
18
20
|
var PUBLIC_SUPPORT_URLS = /* @__PURE__ */ new Set([SUPPORT.x, SUPPORT.discord]);
|
|
@@ -138,18 +140,66 @@ function maskAddress(address) {
|
|
|
138
140
|
return `${s.slice(0, 6)}...${s.slice(-4)}`;
|
|
139
141
|
}
|
|
140
142
|
|
|
141
|
-
// scripts/src/lib/
|
|
143
|
+
// scripts/src/lib/version.mjs
|
|
142
144
|
import { createRequire } from "node:module";
|
|
145
|
+
var PKG_VERSION = (() => {
|
|
146
|
+
const requireFrom = createRequire(import.meta.url);
|
|
147
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
148
|
+
try {
|
|
149
|
+
const pkg = requireFrom(candidate);
|
|
150
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return "0.0.0";
|
|
155
|
+
})();
|
|
143
156
|
|
|
144
157
|
// scripts/src/lib/http.mjs
|
|
145
158
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
146
|
-
var USER_AGENT =
|
|
147
|
-
|
|
159
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
160
|
+
var MAX_RETRIES = 3;
|
|
161
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
162
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
163
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
164
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
165
|
+
if (value === null || value === void 0) return null;
|
|
166
|
+
const s = String(value).trim();
|
|
167
|
+
if (!s) return null;
|
|
168
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
169
|
+
const at = Date.parse(s);
|
|
170
|
+
if (!Number.isFinite(at)) return null;
|
|
171
|
+
return Math.max(0, at - now);
|
|
172
|
+
}
|
|
173
|
+
function bodyRetryAfterMs(json) {
|
|
174
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
175
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
176
|
+
const n = Number(raw);
|
|
177
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
178
|
+
}
|
|
179
|
+
function rateLimitInfo(res) {
|
|
180
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
181
|
+
const info = {
|
|
182
|
+
limit: pick("x-ratelimit-limit"),
|
|
183
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
184
|
+
tier: pick("x-ratelimit-tier"),
|
|
185
|
+
scope: pick("x-ratelimit-scope")
|
|
186
|
+
};
|
|
187
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
188
|
+
return Object.keys(present).length ? present : null;
|
|
189
|
+
}
|
|
190
|
+
function parseBody(text) {
|
|
191
|
+
if (!text) return null;
|
|
192
|
+
try {
|
|
193
|
+
return JSON.parse(text);
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
148
199
|
const controller = new AbortController();
|
|
149
200
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
150
|
-
let res;
|
|
151
201
|
try {
|
|
152
|
-
res = await fetch(url, {
|
|
202
|
+
const res = await fetch(url, {
|
|
153
203
|
method,
|
|
154
204
|
headers: {
|
|
155
205
|
accept: "application/json",
|
|
@@ -159,6 +209,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
159
209
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
160
210
|
signal: controller.signal
|
|
161
211
|
});
|
|
212
|
+
const text = await res.text();
|
|
213
|
+
return { res, text };
|
|
162
214
|
} catch (err) {
|
|
163
215
|
throw new SkillError(
|
|
164
216
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -168,31 +220,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
168
220
|
} finally {
|
|
169
221
|
clearTimeout(timer);
|
|
170
222
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
223
|
+
}
|
|
224
|
+
async function request(method, url, {
|
|
225
|
+
body,
|
|
226
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
227
|
+
sleep = defaultSleep,
|
|
228
|
+
now = Date.now,
|
|
229
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
230
|
+
} = {}) {
|
|
231
|
+
let waitedMs = 0;
|
|
232
|
+
let retries = 0;
|
|
233
|
+
for (; ; ) {
|
|
234
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
235
|
+
const json = parseBody(text);
|
|
236
|
+
if (res.ok) {
|
|
237
|
+
if (json === null) {
|
|
238
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
239
|
+
url: redactUrl(url),
|
|
240
|
+
snippet: redact(text).slice(0, 400)
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return json;
|
|
244
|
+
}
|
|
245
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
246
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
247
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
248
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
249
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
250
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
251
|
+
retries += 1;
|
|
252
|
+
waitedMs += waitMs;
|
|
253
|
+
await sleep(waitMs);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
178
256
|
}
|
|
179
|
-
}
|
|
180
|
-
if (!res.ok) {
|
|
181
257
|
const code = json?.code || json?.error?.code || (typeof json?.error === "string" && /^[A-Z][A-Z0-9_]+$/.test(json.error) ? json.error : null) || `HTTP_${res.status}`;
|
|
182
258
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
259
|
+
const rateLimit = rateLimitInfo(res);
|
|
183
260
|
throw new SkillError(code, redact(String(message)), {
|
|
184
261
|
httpStatus: res.status,
|
|
185
262
|
url: redactUrl(url),
|
|
186
|
-
body: json ?? redact(text).slice(0, 800)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
snippet: redact(text).slice(0, 400)
|
|
263
|
+
body: json ?? redact(text).slice(0, 800),
|
|
264
|
+
...retryable ? {
|
|
265
|
+
retries,
|
|
266
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
267
|
+
} : {},
|
|
268
|
+
...rateLimit ? { rateLimit } : {}
|
|
193
269
|
});
|
|
194
270
|
}
|
|
195
|
-
return json;
|
|
196
271
|
}
|
|
197
272
|
function redactUrl(url) {
|
|
198
273
|
try {
|
|
@@ -258,17 +333,6 @@ function findByLinkId(linkId) {
|
|
|
258
333
|
}
|
|
259
334
|
|
|
260
335
|
// scripts/src/lib/api.mjs
|
|
261
|
-
var PKG_VERSION = (() => {
|
|
262
|
-
const requireFrom = createRequire(import.meta.url);
|
|
263
|
-
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
264
|
-
try {
|
|
265
|
-
const pkg = requireFrom(candidate);
|
|
266
|
-
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
267
|
-
} catch {
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return "0.0.0";
|
|
271
|
-
})();
|
|
272
336
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
273
337
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
274
338
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
@@ -619,6 +683,8 @@ var MARGINS_MS = {
|
|
|
619
683
|
};
|
|
620
684
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
621
685
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
686
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
687
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
622
688
|
function formatRemaining(ms) {
|
|
623
689
|
if (!Number.isFinite(ms)) return "unknown";
|
|
624
690
|
if (ms <= 0) return "expired";
|
package/scripts/dist/resume.js
CHANGED
|
@@ -13,6 +13,8 @@ var SUPPORT = Object.freeze({
|
|
|
13
13
|
discord: "https://discord.gg/EfWejgTbuU"
|
|
14
14
|
});
|
|
15
15
|
var SUPPORT_TEXT = `Need help? Email ${SUPPORT.email}, or reach ROZO on X ${SUPPORT.x} or Discord ${SUPPORT.discord}.`;
|
|
16
|
+
var BULK_DOCS_URL = "https://docs.rozo.ai/products/checkout/bulk-and-agents";
|
|
17
|
+
var BULK_HINT_TEXT = `Buying regularly or for others? ${BULK_DOCS_URL}`;
|
|
16
18
|
|
|
17
19
|
// scripts/src/lib/output.mjs
|
|
18
20
|
var PUBLIC_SUPPORT_URLS = /* @__PURE__ */ new Set([SUPPORT.x, SUPPORT.discord]);
|
|
@@ -160,18 +162,66 @@ function maskMemo(memo) {
|
|
|
160
162
|
return `${s.slice(0, 4)}...${s.slice(-3)}`;
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
// scripts/src/lib/
|
|
165
|
+
// scripts/src/lib/version.mjs
|
|
164
166
|
import { createRequire } from "node:module";
|
|
167
|
+
var PKG_VERSION = (() => {
|
|
168
|
+
const requireFrom = createRequire(import.meta.url);
|
|
169
|
+
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
170
|
+
try {
|
|
171
|
+
const pkg = requireFrom(candidate);
|
|
172
|
+
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return "0.0.0";
|
|
177
|
+
})();
|
|
165
178
|
|
|
166
179
|
// scripts/src/lib/http.mjs
|
|
167
180
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
168
|
-
var USER_AGENT =
|
|
169
|
-
|
|
181
|
+
var USER_AGENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
182
|
+
var MAX_RETRIES = 3;
|
|
183
|
+
var MAX_TOTAL_WAIT_MS = 12e4;
|
|
184
|
+
var FALLBACK_BACKOFF_MS = [2e3, 4e3, 8e3];
|
|
185
|
+
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
186
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
187
|
+
if (value === null || value === void 0) return null;
|
|
188
|
+
const s = String(value).trim();
|
|
189
|
+
if (!s) return null;
|
|
190
|
+
if (/^\d+(\.\d+)?$/.test(s)) return Math.round(Number(s) * 1e3);
|
|
191
|
+
const at = Date.parse(s);
|
|
192
|
+
if (!Number.isFinite(at)) return null;
|
|
193
|
+
return Math.max(0, at - now);
|
|
194
|
+
}
|
|
195
|
+
function bodyRetryAfterMs(json) {
|
|
196
|
+
const raw = json?.retryAfterSeconds ?? json?.error?.retryAfterSeconds;
|
|
197
|
+
if (raw === null || raw === void 0 || raw === "") return null;
|
|
198
|
+
const n = Number(raw);
|
|
199
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : null;
|
|
200
|
+
}
|
|
201
|
+
function rateLimitInfo(res) {
|
|
202
|
+
const pick = (name) => res.headers?.get?.(name) ?? null;
|
|
203
|
+
const info = {
|
|
204
|
+
limit: pick("x-ratelimit-limit"),
|
|
205
|
+
remaining: pick("x-ratelimit-remaining"),
|
|
206
|
+
tier: pick("x-ratelimit-tier"),
|
|
207
|
+
scope: pick("x-ratelimit-scope")
|
|
208
|
+
};
|
|
209
|
+
const present = Object.fromEntries(Object.entries(info).filter(([, v]) => v !== null));
|
|
210
|
+
return Object.keys(present).length ? present : null;
|
|
211
|
+
}
|
|
212
|
+
function parseBody(text) {
|
|
213
|
+
if (!text) return null;
|
|
214
|
+
try {
|
|
215
|
+
return JSON.parse(text);
|
|
216
|
+
} catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function fetchOnce(method, url, body, timeoutMs) {
|
|
170
221
|
const controller = new AbortController();
|
|
171
222
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
172
|
-
let res;
|
|
173
223
|
try {
|
|
174
|
-
res = await fetch(url, {
|
|
224
|
+
const res = await fetch(url, {
|
|
175
225
|
method,
|
|
176
226
|
headers: {
|
|
177
227
|
accept: "application/json",
|
|
@@ -181,6 +231,8 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
181
231
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
182
232
|
signal: controller.signal
|
|
183
233
|
});
|
|
234
|
+
const text = await res.text();
|
|
235
|
+
return { res, text };
|
|
184
236
|
} catch (err) {
|
|
185
237
|
throw new SkillError(
|
|
186
238
|
err?.name === "AbortError" ? "HTTP_TIMEOUT" : "HTTP_UNREACHABLE",
|
|
@@ -190,31 +242,54 @@ async function request(method, url, { body, timeoutMs = DEFAULT_TIMEOUT_MS } = {
|
|
|
190
242
|
} finally {
|
|
191
243
|
clearTimeout(timer);
|
|
192
244
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
245
|
+
}
|
|
246
|
+
async function request(method, url, {
|
|
247
|
+
body,
|
|
248
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
249
|
+
sleep = defaultSleep,
|
|
250
|
+
now = Date.now,
|
|
251
|
+
maxTotalWaitMs = MAX_TOTAL_WAIT_MS
|
|
252
|
+
} = {}) {
|
|
253
|
+
let waitedMs = 0;
|
|
254
|
+
let retries = 0;
|
|
255
|
+
for (; ; ) {
|
|
256
|
+
const { res, text } = await fetchOnce(method, url, body, timeoutMs);
|
|
257
|
+
const json = parseBody(text);
|
|
258
|
+
if (res.ok) {
|
|
259
|
+
if (json === null) {
|
|
260
|
+
throw new SkillError("HTTP_BAD_JSON", "Endpoint returned a non-JSON body.", {
|
|
261
|
+
url: redactUrl(url),
|
|
262
|
+
snippet: redact(text).slice(0, 400)
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return json;
|
|
266
|
+
}
|
|
267
|
+
const headerWaitMs = parseRetryAfter(res.headers?.get?.("retry-after"), now());
|
|
268
|
+
const hintMs = headerWaitMs ?? bodyRetryAfterMs(json);
|
|
269
|
+
const retryable = res.status === 429 || res.status === 503 && hintMs !== null;
|
|
270
|
+
if (retryable && retries < MAX_RETRIES) {
|
|
271
|
+
const waitMs = hintMs ?? FALLBACK_BACKOFF_MS[retries];
|
|
272
|
+
if (waitedMs + waitMs <= Math.min(maxTotalWaitMs, MAX_TOTAL_WAIT_MS)) {
|
|
273
|
+
retries += 1;
|
|
274
|
+
waitedMs += waitMs;
|
|
275
|
+
await sleep(waitMs);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
200
278
|
}
|
|
201
|
-
}
|
|
202
|
-
if (!res.ok) {
|
|
203
279
|
const code = json?.code || json?.error?.code || (typeof json?.error === "string" && /^[A-Z][A-Z0-9_]+$/.test(json.error) ? json.error : null) || `HTTP_${res.status}`;
|
|
204
280
|
const message = json?.message || (typeof json?.error === "string" ? json.error : json?.error?.message) || `HTTP ${res.status}`;
|
|
281
|
+
const rateLimit = rateLimitInfo(res);
|
|
205
282
|
throw new SkillError(code, redact(String(message)), {
|
|
206
283
|
httpStatus: res.status,
|
|
207
284
|
url: redactUrl(url),
|
|
208
|
-
body: json ?? redact(text).slice(0, 800)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
snippet: redact(text).slice(0, 400)
|
|
285
|
+
body: json ?? redact(text).slice(0, 800),
|
|
286
|
+
...retryable ? {
|
|
287
|
+
retries,
|
|
288
|
+
retryAfterSeconds: hintMs === null ? null : Math.ceil(hintMs / 1e3)
|
|
289
|
+
} : {},
|
|
290
|
+
...rateLimit ? { rateLimit } : {}
|
|
215
291
|
});
|
|
216
292
|
}
|
|
217
|
-
return json;
|
|
218
293
|
}
|
|
219
294
|
function redactUrl(url) {
|
|
220
295
|
try {
|
|
@@ -280,17 +355,6 @@ function findByLinkId(linkId) {
|
|
|
280
355
|
}
|
|
281
356
|
|
|
282
357
|
// scripts/src/lib/api.mjs
|
|
283
|
-
var PKG_VERSION = (() => {
|
|
284
|
-
const requireFrom = createRequire(import.meta.url);
|
|
285
|
-
for (const candidate of ["../../package.json", "../../../package.json"]) {
|
|
286
|
-
try {
|
|
287
|
-
const pkg = requireFrom(candidate);
|
|
288
|
-
if (pkg?.name === "@rozoai/checkout" && pkg.version) return pkg.version;
|
|
289
|
-
} catch {
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
return "0.0.0";
|
|
293
|
-
})();
|
|
294
358
|
var MPP_BASE = process.env.ROZO_CHECKOUT_MPP_BASE || "https://apiserver.mpprouter.dev/v1/services/rozo-agent-api";
|
|
295
359
|
var CLIENT_LABEL = `rozo-checkout-cli/${PKG_VERSION}`;
|
|
296
360
|
var ATTRIBUTION_CLIENT = `rozo-checkout-skill/${PKG_VERSION}`;
|
|
@@ -895,6 +959,8 @@ var MARGINS_MS = {
|
|
|
895
959
|
};
|
|
896
960
|
var BOLT11_MIN_VALIDITY_MS = 10 * MINUTE;
|
|
897
961
|
var DEFAULT_MARGIN_MS = 10 * MINUTE;
|
|
962
|
+
var DEFAULT_WATCH_MS = 10 * MINUTE;
|
|
963
|
+
var LIGHTNING_WATCH_CAP_MS = 60 * MINUTE;
|
|
898
964
|
function formatRemaining(ms) {
|
|
899
965
|
if (!Number.isFinite(ms)) return "unknown";
|
|
900
966
|
if (ms <= 0) return "expired";
|