serpnode-js
Version:
Serpnode.com API client for Node.js
111 lines (92 loc) • 2.96 kB
JavaScript
const DEFAULT_BASE_URL = "https://api.serpnode.com/v1";
function buildQueryString(params) {
const searchParams = new URLSearchParams();
for (const [key, value] of Object.entries(params || {})) {
if (value === undefined || value === null) continue;
if (Array.isArray(value)) {
for (const v of value) {
if (v === undefined || v === null) continue;
searchParams.append(key, String(v));
}
} else if (typeof value === "object") {
searchParams.append(key, JSON.stringify(value));
} else {
searchParams.append(key, String(value));
}
}
return searchParams.toString();
}
export class SerpnodeClient {
constructor(options) {
const {
apiKey,
baseUrl = DEFAULT_BASE_URL,
authInQuery = false,
timeoutMs = 30000,
defaultHeaders = {},
} = options || {};
if (!apiKey || typeof apiKey !== "string") {
throw new Error("SerpnodeClient: 'apiKey' is required and must be a string");
}
this.apiKey = apiKey;
this.baseUrl = baseUrl.replace(/\/$/, "");
this.authInQuery = Boolean(authInQuery);
this.timeoutMs = Number(timeoutMs) || 30000;
this.defaultHeaders = { ...defaultHeaders };
}
setApiKey(apiKey) {
if (!apiKey || typeof apiKey !== "string") {
throw new Error("SerpnodeClient.setApiKey: 'apiKey' must be a non-empty string");
}
this.apiKey = apiKey;
}
async _get(path, params) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
try {
const allParams = { ...(params || {}) };
if (this.authInQuery) {
allParams["apikey"] = this.apiKey;
}
const qs = buildQueryString(allParams);
const url = `${this.baseUrl}/${path}${qs ? `?${qs}` : ""}`;
const headers = {
Accept: "application/json",
...this.defaultHeaders,
};
if (!this.authInQuery) {
headers["apikey"] = this.apiKey;
}
const response = await fetch(url, {
method: "GET",
headers,
signal: controller.signal,
});
const contentType = response.headers.get("content-type") || "";
const isJson = contentType.includes("application/json");
const body = isJson ? await response.json().catch(() => undefined) : await response.text();
if (!response.ok) {
const error = new Error(`Serpnode API error: ${response.status} ${response.statusText}`);
error.status = response.status;
error.details = body;
throw error;
}
return body;
} finally {
clearTimeout(timeout);
}
}
async status() {
return this._get("status", {});
}
async search(params) {
return this._get("search", params);
}
async options(params) {
return this._get("options", params);
}
async locations(params) {
return this._get("locations", params);
}
}
export default SerpnodeClient;