UNPKG

mindee

Version:

Mindee Client Library for Node.js

96 lines (95 loc) 4.28 kB
import { InputSource } from "./inputSource.js"; import { URL } from "url"; import { basename, extname } from "path"; import { randomBytes } from "crypto"; import { writeFile } from "fs/promises"; import { request, getGlobalDispatcher } from "undici"; import { logger } from "../logger.js"; import { MindeeInputSourceError } from "../errors/index.js"; import { BytesInput } from "./bytesInput.js"; export class UrlInput extends InputSource { constructor({ url, dispatcher }) { super(); this.url = url; this.dispatcher = dispatcher ?? getGlobalDispatcher(); logger.debug("Initialized URL input source."); } async init() { if (this.initialized) { return; } logger.debug(`source URL: ${this.url}`); if (!this.url.toLowerCase().startsWith("https")) { throw new MindeeInputSourceError("URL must be HTTPS"); } this.fileObject = this.url; this.initialized = true; } async fetchFileContent(options) { const { username, password, token, headers = {}, maxRedirects = 3 } = options; if (token) { headers["Authorization"] = `Bearer ${token}`; } const auth = username && password ? `${username}:${password}` : undefined; return await this.makeRequest(this.url, auth, headers, 0, maxRedirects); } async saveToFile(options) { const { filepath, filename, ...fetchOptions } = options; const { content, finalUrl } = await this.fetchFileContent(fetchOptions); const finalFilename = this.fillFilename(filename, finalUrl); const fullPath = `${filepath}/${finalFilename}`; await writeFile(fullPath, content); return fullPath; } async asLocalInputSource(options = {}) { const { filename, ...fetchOptions } = options; const { content, finalUrl } = await this.fetchFileContent(fetchOptions); const finalFilename = this.fillFilename(filename, finalUrl); return new BytesInput({ inputBytes: content, filename: finalFilename }); } static extractFilenameFromUrl(uri) { return basename(new URL(uri).pathname || ""); } static generateFileName(extension = ".tmp") { const randomString = randomBytes(4).toString("hex"); const timestamp = new Date().toISOString().replace(/[-:]/g, "").split(".")[0]; return `mindee_temp_${timestamp}_${randomString}${extension}`; } static getFileExtension(filename) { const ext = extname(filename); return ext ? ext.toLowerCase() : null; } fillFilename(filename, finalUrl) { if (!filename) { filename = finalUrl ? UrlInput.extractFilenameFromUrl(finalUrl) : UrlInput.extractFilenameFromUrl(this.url); } if (!filename || !extname(filename)) { filename = UrlInput.generateFileName(UrlInput.getFileExtension(filename || "") || undefined); } return filename; } async makeRequest(url, auth, headers, redirects, maxRedirects) { const parsedUrl = new URL(url); const response = await request(parsedUrl, { method: "GET", headers: headers, throwOnError: false, dispatcher: this.dispatcher, }); if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400) { logger.debug(`Redirecting to: ${response.headers.location}`); if (redirects === maxRedirects) { throw new MindeeInputSourceError(`Can't reach URL after ${redirects} out of ${maxRedirects} redirects, aborting operation.`); } if (response.headers.location) { return await this.makeRequest(response.headers.location.toString(), auth, headers, redirects + 1, maxRedirects); } throw new MindeeInputSourceError("Redirect location not found"); } if (!response.statusCode || response.statusCode >= 400 || response.statusCode < 200) { throw new Error(`Couldn't retrieve file from server, error code ${response.statusCode}.`); } const arrayBuffer = await response.body.arrayBuffer(); return { content: Buffer.from(arrayBuffer), finalUrl: url }; } }