UNPKG

brave-search

Version:

A fully typed Brave Search API wrapper, providing easy access to web search, local POI search, and automatic polling for web search summary feature.

269 lines (268 loc) 11.1 kB
"use strict"; // Copyright (C) 2024 Erik Balfe // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BraveSearchError = exports.BraveSearch = void 0; const axios_1 = __importDefault(require("axios")); const DEFAULT_POLLING_INTERVAL = 500; const DEFAULT_MAX_POLL_ATTEMPTS = 20; /** * An error class specific to BraveSearch API interactions. * It includes additional information about the response data that caused the error. */ class BraveSearchError extends Error { /** * Initializes a new instance of the BraveSearchError class. * @param message The error message. * @param responseData The response data that caused the error. */ constructor(message, responseData) { super(message); this.name = "BraveSearchError"; this.responseData = responseData; } } exports.BraveSearchError = BraveSearchError; /** * The main class for interacting with the Brave Search API, holding API key for all the requests made with it. * It provides methods for web search, image search, local POI search, and summarization. */ class BraveSearch { /** * Initializes a new instance of the BraveSearch class. * @param apiKey The API key for accessing the Brave Search API. * @param options */ constructor(apiKey, options) { var _a, _b; this.baseUrl = "https://api.search.brave.com/res/v1"; this.apiKey = apiKey; this.pollInterval = (_a = options === null || options === void 0 ? void 0 : options.pollInterval) !== null && _a !== void 0 ? _a : DEFAULT_POLLING_INTERVAL; this.maxPollAttempts = (_b = options === null || options === void 0 ? void 0 : options.maxPollAttempts) !== null && _b !== void 0 ? _b : DEFAULT_MAX_POLL_ATTEMPTS; } /** * Performs a web search using the provided query and options. * @param query The search query string. * @param options Optional settings to configure the search behavior. * @returns A promise that resolves to the search results. */ async webSearch(query, options = {}, signal) { try { const response = await axios_1.default.get(`${this.baseUrl}/web/search`, { params: { q: query, ...this.formatOptions(options), }, headers: this.getHeaders(), signal, }); return response.data; } catch (error) { const handledError = this.handleApiError(error); throw handledError; } } /** * Performs an image search using the provided query and options. * @param query The search query string. * @param options Optional settings to configure the search behavior. * @returns A promise that resolves to the image search results. */ async imageSearch(query, options = {}, signal) { try { const response = await axios_1.default.get(`${this.baseUrl}/images/search`, { params: { q: query, ...this.formatOptions(options), }, headers: this.getHeaders(), signal, }); return response.data; } catch (error) { const handledError = this.handleApiError(error); throw handledError; } } async newsSearch(query, options = {}, signal) { try { const response = await axios_1.default.get(`${this.baseUrl}/news/search?`, { params: { q: query, ...this.formatOptions(options), }, headers: this.getHeaders(), signal, }); return response.data; } catch (error) { const handledError = this.handleApiError(error); throw handledError; } } /** * Executes a web search for the provided query and polls for a summary * if the query is eligible for a summary and summarizer key is provided in the web search response. * The summary is usually ready within 2 seconds after the original web search response is received. * @param query The search query string. * @param options Optional settings to configure the search behavior. * @param summarizerOptions Optional settings specific to summarization. * @returns An object containing promises for the web search results and the summarized answer. */ getSummarizedAnswer(query, options = {}, summarizerOptions = {}, signal) { try { const webSearchResponse = this.webSearch(query, options, signal); const summaryPromise = webSearchResponse.then(async (webSearchResponse) => { var _a; const summarizerKey = (_a = webSearchResponse.summarizer) === null || _a === void 0 ? void 0 : _a.key; if (summarizerKey) { return await this.pollForSummary(summarizerKey, summarizerOptions, signal); } return undefined; }); return { webSearch: webSearchResponse, summary: summaryPromise }; } catch (error) { throw this.handleApiError(error); } } /** * Searches for local points of interest using the provided IDs and options. * @param ids The IDs of the local points of interest. * @returns A promise that resolves to the search results. */ async localPoiSearch(ids, signal) { try { const response = await axios_1.default.get(`${this.baseUrl}/local/pois`, { params: { ids: ids.join(","), }, headers: this.getHeaders(), signal, }); return response.data; } catch (error) { throw this.handleApiError(error); } } /** * Retrieves descriptions for local points of interest using the provided IDs and options. * @param ids The IDs of the local points of interest. * @returns A promise that resolves to the search results. */ async localDescriptionsSearch(ids, signal) { try { const response = await axios_1.default.get(`${this.baseUrl}/local/descriptions`, { params: { ids: ids.join(","), }, headers: this.getHeaders(), signal, }); return response.data; } catch (error) { throw this.handleApiError(error); } } /** * Polls for a summary response after a web search request. This method is suggested by the Brave Search API documentation * as the way to retrieve a summary after initiating a web search. * * @param key The key identifying the summary request. * @param options Optional settings specific to summarization. * @param signal Optional AbortSignal to cancel the request. * @returns A promise that resolves to the summary response if available, or undefined if the summary is not ready. * @throws {BraveSearchError} If the summary generation fails or if the summary is not available after maximum polling attempts. * * **Polling Behavior:** * - The method will make up to 20 attempts to fetch the summary by default. * - Each attempt is spaced 500ms apart. * - If the summary is not ready after 20 attempts, a BraveSearchError is thrown. * * **Configuration:** * - The number of attempts and the interval between attempts can be configured through the class constructor options. */ async pollForSummary(key, options, signal) { for (let attempt = 0; attempt < this.maxPollAttempts; attempt++) { const summaryResponse = await this.summarizerSearch(key, options, signal); if (summaryResponse.status === "complete" && summaryResponse.summary) { return summaryResponse; } else if (summaryResponse.status === "failed") { throw new BraveSearchError("Summary generation failed"); } await new Promise((resolve) => setTimeout(resolve, this.pollInterval)); } throw new BraveSearchError("Summary not available after maximum polling attempts"); } async summarizerSearch(key, options, signal) { try { const response = await axios_1.default.get(`${this.baseUrl}/summarizer/search`, { params: { key, ...this.formatOptions(options), }, headers: this.getHeaders(), signal, }); return response.data; } catch (error) { throw this.handleApiError(error); } } getHeaders() { return { Accept: "application/json", "Accept-Encoding": "gzip", "X-Subscription-Token": this.apiKey, }; } formatOptions(options) { return Object.entries(options).reduce((acc, [key, value]) => { if (value !== undefined) { acc[key] = value.toString(); } return acc; }, {}); } handleApiError(error) { var _a, _b, _c, _d; if (axios_1.default.isAxiosError(error)) { const status = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status; const message = ((_c = (_b = error.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.message) || error.message; const responseData = (_d = error.response) === null || _d === void 0 ? void 0 : _d.data; if (status === 429) { return new BraveSearchError(`Rate limit exceeded: ${message}`, responseData); } else if (status === 401) { return new BraveSearchError(`Authentication error: ${message}`, responseData); } else { return new BraveSearchError(`API error (${status}): ${message}`, responseData); } } return new BraveSearchError(`Unexpected error: ${error.message}`); } } exports.BraveSearch = BraveSearch;