109 lines
3.7 KiB
JavaScript
109 lines
3.7 KiB
JavaScript
"use strict";
|
|
/**
|
|
* Suno API HTTP client with rate limiting and polling helpers.
|
|
*
|
|
* Rate limit: 20 requests per 10 seconds (sunoapi.org).
|
|
* Proxy support via SUNO_HTTP_PROXY / HTTPS_PROXY / HTTP_PROXY env vars.
|
|
*/
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.SunoClient = void 0;
|
|
const undici_1 = require("undici");
|
|
function getProxyUrl() {
|
|
return (process.env.SUNO_HTTP_PROXY ||
|
|
process.env.HTTPS_PROXY ||
|
|
process.env.https_proxy ||
|
|
process.env.HTTP_PROXY ||
|
|
process.env.http_proxy);
|
|
}
|
|
class SunoClient {
|
|
baseUrl = 'https://api.sunoapi.org/api/v1';
|
|
apiKey;
|
|
maxRequests = 20;
|
|
windowMs = 10_000;
|
|
proxyAgent;
|
|
requestTimestamps = [];
|
|
constructor(apiKey) {
|
|
this.apiKey = apiKey;
|
|
const proxyUrl = getProxyUrl();
|
|
if (proxyUrl) {
|
|
this.proxyAgent = new undici_1.ProxyAgent(proxyUrl);
|
|
}
|
|
}
|
|
/**
|
|
* Enforce rate limit: max 20 requests per 10 seconds.
|
|
*/
|
|
async throttle() {
|
|
const now = Date.now();
|
|
this.requestTimestamps = this.requestTimestamps.filter((ts) => now - ts < this.windowMs);
|
|
if (this.requestTimestamps.length >= this.maxRequests) {
|
|
const oldest = this.requestTimestamps[0];
|
|
const wait = this.windowMs - (now - oldest);
|
|
if (wait > 0) {
|
|
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
return this.throttle();
|
|
}
|
|
}
|
|
this.requestTimestamps.push(now);
|
|
}
|
|
async request(method, path, body) {
|
|
await this.throttle();
|
|
const url = path.startsWith('http') ? path : `${this.baseUrl}${path}`;
|
|
const headers = {
|
|
Authorization: `Bearer ${this.apiKey}`,
|
|
'Content-Type': 'application/json',
|
|
};
|
|
const fetchOptions = {
|
|
method,
|
|
headers,
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
};
|
|
if (this.proxyAgent) {
|
|
fetchOptions.dispatcher = this.proxyAgent;
|
|
}
|
|
const response = await fetch(url, fetchOptions);
|
|
const text = await response.text();
|
|
let json;
|
|
try {
|
|
json = JSON.parse(text);
|
|
}
|
|
catch {
|
|
throw new Error(`Invalid JSON response from Suno API: ${text}`);
|
|
}
|
|
if (json.code !== 200) {
|
|
throw new Error(`Suno API error ${json.code}: ${json.msg || 'unknown error'}`);
|
|
}
|
|
return json;
|
|
}
|
|
get(path) {
|
|
return this.request('GET', path);
|
|
}
|
|
post(path, body) {
|
|
return this.request('POST', path, body);
|
|
}
|
|
/**
|
|
* Poll task status until terminal state or timeout.
|
|
*/
|
|
async pollTaskStatus(taskId, options = {}) {
|
|
const intervalMs = options.intervalMs ?? 15_000;
|
|
const maxAttempts = options.maxAttempts ?? 60;
|
|
const statusPath = options.statusPath ?? `/generate/record-info?taskId=${encodeURIComponent(taskId)}`;
|
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
const result = await this.get(statusPath);
|
|
const status = result.data.status;
|
|
if (status === 'SUCCESS' ||
|
|
status === 'FAILED' ||
|
|
status === 'CREATE_TASK_FAILED' ||
|
|
status === 'GENERATE_AUDIO_FAILED' ||
|
|
status === 'CALLBACK_EXCEPTION' ||
|
|
status === 'SENSITIVE_WORD_ERROR') {
|
|
return result.data;
|
|
}
|
|
if (attempt < maxAttempts) {
|
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
}
|
|
}
|
|
throw new Error(`Polling timeout for task ${taskId} after ${maxAttempts} attempts`);
|
|
}
|
|
}
|
|
exports.SunoClient = SunoClient;
|
|
//# sourceMappingURL=client.js.map
|