// /** // * @swagger // * /api/documents/download/{id}: // * get: // * tags: // * - Documents // * description: Get document // * parameters: // * - name: id // * in: path // * description: iShare ID // * type: string // * required: true // * default: A41567436 // * - name: hash // * in: query // * description: Hash string // * default: e0a1c9cd2817826a25bca67e60b807eae747cbed769e3ec42cf997541c32be71 // * responses: // * 200: // * description: Success // */ // import axios from "axios"; // import CryptoJS from "crypto-js"; // const WORDKEY = process.env.HASHKEY; // const accessTokenEndpoint = process.env.ACCESS_TOKEN_ENDPOINT; // const tenantId = process.env.TENANT; // const WEBAPI_URL = // process.env.RELAY_ROOT || // "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; // const hashAPIPath = (queryPath) => { // var hashlink = CryptoJS.HmacSHA256( // queryPath, // CryptoJS.enc.Hex.parse(WORDKEY) // ); // hashlink = hashlink.toString(CryptoJS.enc.Hex); // //return "&hash=" + hashlink; // return (queryPath.indexOf("?") > -1 ? "&hash=" : "?hash=") + hashlink; // }; // const azureHeadersPaged = (access_token) => { // return { // headers: { // "OData-MaxVersion": "4.0", // "OData-Version": "4.0", // "Accept": "application/json;odata.metadata=none", // "Prefer": // 'odata.include-annotations="*",return=representation, odata.maxpagesize=10', // "Content-Type": "application/json", // "Authorization": "Bearer " + access_token, // }, // }; // }; // export default async function ApiProxy(req, res) { // var token = await getToken(); // var docRef = req.query; // var queryUrl = "documents/download/" + docRef.id + "?hash=" + docRef.hash; // const configDocument = (access_token) => { // return { // headers: { // "OData-MaxVersion": "4.0", // "OData-Version": "4.0", // "Accept": "application/json", // "Prefer": 'odata.include-annotations="*",return=representation', // "Content-Type": "application/json", // "Authorization": "Bearer " + access_token, // }, // responseType: "arraybuffer", // }; // }; // return axios // .get( // WEBAPI_URL + queryUrl, // + hashAPIPath(queryUrl), // configDocument(token.access_token) // ) // .then((response) => { // const bytes = response.data.byteLength; // console.log(bytes); // res.setHeader( // "content-disposition", // "attachment; filename=" + // response.headers["content-disposition"].split( // "filename=" // )[1] // ); // return res.status(200).send(response.data); // }) // .then(() => { // console.log(docRef.id + "has downloaded"); // return "downloadComplete"; // }) // .catch((error) => { // consoleLogger(error); // res.redirect("/filenotavailable"); // }); // } import axios from "axios"; import { getToken } from "../../../../actions/core/token"; import { consoleLogger } from "../../../../actions/core/logger"; const WEBAPI_URL = process.env.RELAY_ROOT || "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; const FILE_NOT_AVAILABLE_PATH = "/filenotavailable"; // Retry utility with logging async function retry(fn, retries = 3, delay = 1000) { for (let attempt = 1; attempt <= retries; attempt++) { try { return { response: await fn(), attempts: attempt }; } catch (err) { if (attempt === retries) throw err; console.log(`Retry ${attempt} failed, retrying in ${delay}ms...`); await new Promise((res) => setTimeout(res, delay)); delay *= 2; // exponential backoff } } } const ApiProxy = async (req, res) => { const docRef = req.query; if ( typeof docRef?.id !== "string" || docRef.id.length === 0 || typeof docRef?.hash !== "string" || docRef.hash.length === 0 ) { if (!res.headersSent) { res.redirect(FILE_NOT_AVAILABLE_PATH); } return; } try { const token = await getToken(); const startTime = Date.now(); const fetchStream = async () => { const queryUrl = "documents/download/" + docRef.id + "?hash=" + docRef.hash; return axios.get(WEBAPI_URL + queryUrl, { headers: { "OData-MaxVersion": "4.0", "OData-Version": "4.0", "Accept": "application/json", "Prefer": 'odata.include-annotations="*",return=representation', "Content-Type": "application/json", "Authorization": "Bearer " + token.access_token }, responseType: "stream" }); }; const { response, attempts } = await retry(fetchStream, 3, 1000); // Extract filename const contentDisposition = response.headers["content-disposition"]; const filename = contentDisposition ? contentDisposition.split("filename=")[1] : docRef.id; res.setHeader( "Content-Disposition", `attachment; filename=${filename}` ); res.setHeader("Content-Type", "application/octet-stream"); let totalBytes = 0; response.data.on("data", (chunk) => { totalBytes += chunk.length; }); response.data.pipe(res); response.data.on("end", () => { const durationMs = Date.now() - startTime; console.log( `[Download Complete] Document: ${filename}, ID: ${docRef.id}, Size: ${totalBytes} bytes, Duration: ${durationMs}ms, Attempts: ${attempts}` ); }); response.data.on("error", (err) => { consoleLogger(err); if (!res.headersSent) res.redirect(FILE_NOT_AVAILABLE_PATH); }); } catch (error) { consoleLogger(error); if (!res.headersSent) res.redirect(FILE_NOT_AVAILABLE_PATH); } }; export const config = { api: { responseLimit: false } }; export default ApiProxy;