UNPKG

jetpath

Version:

A performance-first cross-runtime API framework without the boilerplate

1,409 lines (1,340 loc) 49.5 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>{NAME} API</title> <link rel="shortcut icon" href="https://raw.githubusercontent.com/codedynasty-dev/jetpath/main/icon.png" type="image/png" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;700&family=Roboto:wght@300;400;500&display=swap"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pretty-print-json@3.0/dist/css/pretty-print-json.css"> <script src="https://cdn.jsdelivr.net/npm/pretty-print-json@3.0/dist/pretty-print-json.min.js"></script> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" /> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" defer></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" defer></script> <script src="https://cdn.jsdelivr.net/gh/adamvleggett/drawdown/drawdown.js"></script> <style> :root { --primary-color: JETPATHCOLOR; --primary-light: JETPATHCOLOR36; --primary-lighter: JETPATHCOLOR2e; --primary-dark: JETPATHCOLORb6; --primary-border: JETPATHCOLOR5e; --text-primary: #202124; --text-secondary: #5f6368; --surface-1: #ffffff; --surface-2: #f8f9fa; --surface-3: #f1f3f4; --border-color: #dadce0; --error-color: #d93025; --success-color: #1e8e3e; --shadow-1: 0 1px 2px 0 rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15); --radius-sm: 4px; --radius-md: 8px; --font-family: "Google Sans", "Roboto", -apple-system, BlinkMacSystemFont, sans-serif; } body { font-family: var(--font-family); color: var(--text-primary); background-color: var(--surface-2); line-height: 1.5; font-size: 14px; -webkit-font-smoothing: antialiased; } ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-thumb { background-color: #c1c1c1; border-radius: 8px; } ::-webkit-scrollbar-thumb:hover { background-color: #a8a8a8; } .container-fluid { padding-left: 25px; padding-right: 25px; } header { border-bottom: 1px solid var(--border-color); position: sticky; top: 0; z-index: 1020; box-shadow: var(--shadow-1); background-color: var(--primary-color) !important; color: #fff !important; margin-bottom: 20px; display: flex; align-items: center; padding: 0.8rem 1.5rem; } .header-content { display: flex; align-items: center; gap: 16px; width: 100%; } .header-content h1 { font-size: 20px; line-height: 1.2; color: #fff; margin-bottom: 0; } .project-info { background-color: var(--surface-1); border-radius: var(--radius-md); padding: 15px 20px; margin-bottom: 20px; box-shadow: var(--shadow-1); border: 1px solid var(--border-color); } .project-info span:first-child { font-weight: 500; color: var(--text-secondary); display: block; margin-bottom: 5px; } .section { max-width: 1460px; margin: 0 auto; margin-bottom: 2rem; } .section-title h2 { font-size: 1.25rem; margin-bottom: 1rem; color: var(--text-primary); border-bottom: 2px solid var(--primary-color); padding-bottom: 0.5rem; } .card { background-color: var(--surface-1); border-radius: var(--radius-md); margin-bottom: 10px; border: 1px solid var(--border-color); box-shadow: none; } .card:hover { box-shadow: var(--shadow-1); } .card-header { background-color: var(--surface-1); padding: 0; border-bottom: 1px solid var(--border-color); } .card-header .btn-link { padding: 12px 18px; width: 100%; text-align: left; border: none; background: none; cursor: pointer; display: flex; align-items: center; gap: 10px; color: var(--text-primary); font-weight: 500; text-decoration: none; font-size: 0.95rem; } .card-header .btn-link:hover { background-color: var(--surface-3); } .card-header .btn-link .api-path { word-break: break-all; } .card-header .btn-link::after { content: "\25BC"; font-size: 0.8em; margin-left: auto; transition: transform 0.2s ease; } /* Down arrow */ .card-header .btn-link[aria-expanded="true"]::after { transform: rotate(-180deg); } /* Up arrow */ .card-body { padding: 18px; background-color: var(--surface-2); border-top: 1px solid #e0e0e0; } .input-group-text { background-color: var(--surface-3); border-right: 0; font-weight: 500; font-size: 0.85rem; } input[type="text"], input[type="number"], input[type="file"], select.form-control { border-radius: var(--radius-sm); font-size: 0.9rem; } input[type="text"]:focus, input[type="number"]:focus, input[type="file"]:focus, select.form-control:focus { border-color: var(--primary-color); box-shadow: 0 0 0 2px var(--primary-lighter); } .url-input { font-family: monospace; font-size: 0.9rem; } .btn-sm { padding: .25rem .5rem; font-size: .8rem; } .btn-primary { background-color: var(--primary-color); border-color: var(--primary-color); } .btn-primary:hover { background-color: var(--primary-dark); border-color: var(--primary-dark); } .btn-secondary { color: var(--primary-color); border-color: var(--primary-color); background-color: transparent; } .btn-secondary:hover { background-color: var(--primary-lighter); color: var(--primary-dark); border-color: var(--primary-dark); } span.method { display: inline-flex; align-items: center; justify-content: center; min-width: 60px; height: 22px; border-radius: var(--radius-sm); color: white; font-weight: 500; font-size: 0.75rem; text-transform: uppercase; padding: 0 8px; } span.GET { background-color: #0d6efd; } span.POST { background-color: #198754; } span.PUT { background-color: #ffc107; color: var(--text-primary); } span.DELETE { background-color: #dc3545; } .nav-tabs .nav-link { font-size: 0.9rem; color: var(--text-secondary); border-bottom-width: 2px; } .nav-tabs .nav-link.active { color: var(--primary-color); border-color: var(--primary-color) var(--primary-color) var(--surface-1); font-weight: 500; } .tab-content { padding: 15px; background-color: var(--surface-1); border: 1px solid var(--border-color); border-top: 0; border-radius: 0 0 var(--radius-md) var(--radius-md); } .response-meta { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 10px; } .response-meta span { margin-right: 15px; } .response-meta .status-success { color: var(--success-color); font-weight: bold; } .response-meta .status-error { color: var(--error-color); font-weight: bold; } .code-container { border-radius: var(--radius-sm); overflow: auto; margin-top: 8px; padding: 10px; color: #f0f0f0; max-height: 400px; } .code-container pre { margin: 0; font-size: 0.85rem; } .assertion-result { margin-bottom: 5px; padding: 8px; border-radius: var(--radius-sm); font-size: 0.85rem; } .assertion-pass { background-color: #d1e7dd; color: #0f5132; border-left: 3px solid var(--success-color); } .assertion-fail { background-color: #f8d7da; color: #842029; border-left: 3px solid var(--error-color); } .toast-container { position: fixed; top: 20px; right: 20px; z-index: 1050; } .toast-message { background-color: var(--primary-color); color: white; padding: 10px 15px; border-radius: var(--radius-sm); box-shadow: var(--shadow-1); font-size: 0.9rem; } #api-search-input { margin-bottom: 15px; } #api-history-container .list-group-item { font-size: 0.85rem; } .action-buttons button { margin-right: 10px; } .headers-table { width: 100%; font-size: 0.85rem; } .headers-table th, .headers-table td { padding: 5px; border-bottom: 1px solid var(--surface-3); text-align: left; } .headers-table th { font-weight: 500; background-color: var(--surface-2); } .payload-section, .global-auth-section { font-size: 0.9rem; padding: 10px; background-color: var(--surface-3); border-radius: var(--radius-sm); } .payload-section strong, .global-auth-section strong { display: block; margin-bottom: 8px; } .curl-output pre { color: #f0f0f0; background-color: #1e1e1e; padding: 10px; border-radius: var(--radius-sm); white-space: pre-wrap; word-break: break-all; } </style> </head> <body> <header> <div class="header-content"> <img src="{LOGO}" alt="{NAME} Logo" style="width: 36px; height: 36px;" /> <h1>{NAME} API Documentation</h1> </div> </header> <div class="container-fluid"> <div class="project-info section"> <h2>Project Information</h2> <p id="project-info-text">{INFO}</p> <h2>Project Global Headers</h2> <div id="keys" style="margin-top: 10px;"></div> <div id="env-switcher-container" style="margin-top: 10px;"></div> </div> <div class="section"> <div class="section-title d-flex justify-content-between align-items-center"> <h2>API Endpoints</h2> </div> <input type="text" id="api-search-input" class="form-control" placeholder="Search endpoints (e.g., GET /users)..."> <div class="accordion" id="api-endpoint-container"></div> </div> <div id="api-history-container" class="section"></div> </div> <footer> <div class="container text-center py-3"> <small class="text-muted">&copy; {CURRENT_YEAR} {NAME}. All rights reserved. Powered by Jetpath.</small> </div> </footer> <div class="toast-container" id="toast-container"></div> <script> // src/assets/bundle.ts var infodoc = document.getElementById("project-info-text"); if (infodoc) { infodoc.innerHTML = markdown(infodoc.innerText); } var MAX_HISTORY_ITEMS = 15; var requestHistory = JSON.parse(localStorage.getItem("jetpathApiHistory")) || []; var apiDocumentationRawTemplate = `{ JETPATH }`; var apiGlobalHeadersRaw = `{ JETPATHGH }`; var currentYear = new Date().getFullYear(); if (document.querySelector("footer small")) { document.querySelector("footer small").textContent = document.querySelector("footer small").textContent.replace("{CURRENT_YEAR}", String(currentYear)); } var environments = { "Default (Current Host)": typeof window !== "undefined" ? window.location.origin : "" }; var currentBaseUrl = environments["Default (Current Host)"]; function Rhoda(l) { const fg = new DocumentFragment; for (let ch of l) { if (Array.isArray(ch)) fg.appendChild(Rhoda(ch)); else { if (typeof ch === "function") { ch = ch(); if (typeof ch === "function") ch = ch(); } if (ch instanceof HTMLElement || ch instanceof DocumentFragment) { fg.appendChild(ch); continue; } if (typeof ch === "string") fg.appendChild(document.createTextNode(ch)); } } return fg; } var makeElement = (element, ElementChildrenAndPropertyList) => { const props = {}; let text = undefined; if (ElementChildrenAndPropertyList.length !== 0) { for (let i = 0;i < ElementChildrenAndPropertyList.length; i++) { let ch = ElementChildrenAndPropertyList[i]; if (typeof ch === "function") { ch = ch(); } if (ch instanceof HTMLElement || ch instanceof DocumentFragment) { element.appendChild(ch); continue; } if (Array.isArray(ch)) { element.appendChild(Rhoda(ch)); continue; } if (typeof ch === "string") { text = ch; continue; } if (typeof ch === "object" && ch !== null) { Object.assign(props, ch); continue; } } } else return element; if (typeof props === "object" && element) { for (const [prop, value] of Object.entries(props)) { if (prop === "style" && typeof value === "object") { Object.assign(element.style, value); continue; } if (prop.startsWith("on") && typeof value === "function") { element.addEventListener(prop.substring(2).toLowerCase(), value); continue; } if (prop.includes("data-") || prop.includes("aria-")) { element.setAttribute(prop, value); continue; } element[prop] = value; } } if (text !== undefined) element.appendChild(document.createTextNode(text)); return element; }; var cra = (tag) => (...Children_and_Properties) => makeElement(document.createElement(tag), Children_and_Properties); function $if(condition, ...elements) { if (condition) return Rhoda(elements.flat()); return document.createDocumentFragment(); } var button = cra("button"); var div = cra("div"); var h2 = cra("h2"); var h3 = cra("h3"); var h4 = cra("h4"); var h5 = cra("h5"); var input = cra("input"); var span = cra("span"); var strong = cra("strong"); var pre = cra("pre"); var option = cra("option"); var selectEl = cra("select"); var ul = cra("ul"); var li = cra("li"); var table = cra("table"); var tbody = cra("tbody"); var thead = cra("thead"); var tr = cra("tr"); var th = cra("th"); var td = cra("td"); var a = cra("a"); var label = cra("label"); var small = cra("small"); var p = cra("p"); var loading_svg = () => { const l = document.createElement("span"); l.innerHTML = '<div class="spinner-border spinner-border-sm text-primary" role="status"><span class="sr-only">Loading...</span></div>'; return l; }; function syntaxHighlight(json) { if (typeof json === "string") { try { json = JSON.parse(json); } catch (e) { return `<pre>${escapeHtml(json)}</pre>`; } } if (typeof prettyPrintJson === "undefined") { return `<pre>${escapeHtml(JSON.stringify(json, null, 2))}</pre>`; } return prettyPrintJson.toHtml(json, { indent: 2, lineNumbers: false, linkUrls: true, linksNewTab: true, quoteKeys: true, trailingCommas: false }); } function escapeHtml(unsafe) { return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;"); } function showToast(message) { const toastContainer = document.getElementById("toast-container"); if (!toastContainer) return; const toast = div({ className: "toast-message" }, message); toastContainer.appendChild(toast); setTimeout(() => { toast.remove(); }, 3000); } function copyToClipboard(text, type) { navigator.clipboard.writeText(text).then(() => showToast(`${type} copied to clipboard!`)).catch((err) => { console.error("Failed to copy: ", err); showToast(`Failed to copy ${type}.`); }); } function createEnvironmentSelector() { const container = document.getElementById("env-switcher-container"); if (!container) return; const label2 = h3({ style: { marginRight: "8px", fontWeight: "500" } }, "Environment:"); const selector = selectEl({ id: "env-selector", className: "form-control form-control-sm d-inline-block", style: { width: "auto", minWidth: "200px" }, onchange: (e) => { currentBaseUrl = e.target.value; renderApiEndpoints(); } }); Object.entries(environments).forEach(([name, url]) => { selector.appendChild(option({ value: url }, name)); }); selector.value = currentBaseUrl; container.appendChild(label2); container.appendChild(selector); } function saveToHistory(method, url, status, payloadSummary, time) { const timestamp = new Date().toISOString(); requestHistory.unshift({ method, url, status, timestamp, payloadSummary, time }); if (requestHistory.length > MAX_HISTORY_ITEMS) requestHistory.pop(); localStorage.setItem("jetpathApiHistory", JSON.stringify(requestHistory)); renderHistory(); } function renderHistory() { let historyContainer = document.getElementById("api-history-container"); if (!historyContainer) return; historyContainer.innerHTML = ""; historyContainer.appendChild(h2({ className: "section-title" }, "Request History")); if (requestHistory.length === 0) { historyContainer.appendChild(span("No requests in history yet.")); return; } const list = ul({ className: "list-group" }); requestHistory.forEach((item) => { const listItem = li({ className: "list-group-item list-group-item-action flex-column align-items-start", style: { cursor: "pointer" }, title: "Click to re-populate (basic)", onclick: () => { tryRepopulateFromHistory(item); } }, div({ className: "d-flex w-100 justify-content-between" }, h5({ className: "mb-1" }, `${item.method} ${new URL(item.url).pathname}`), small(`${item.time ? item.time + "ms - " : ""}${new Date(item.timestamp).toLocaleTimeString()}`)), p({ className: "mb-1", style: { fontSize: "0.8rem", wordBreak: "break-all" } }, item.url), small({ className: `status-${item.status < 400 ? "success" : "error"}` }, `Status: ${item.status}`), item.payloadSummary ? small({ className: "d-block text-muted", style: { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, `Payload: ${item.payloadSummary}`) : ""); list.appendChild(listItem); }); historyContainer.appendChild(list); } function tryRepopulateFromHistory(historyItem) { const endpointCard = Array.from(document.querySelectorAll(".card .btn-link")).find((btn) => { const cardUrlPath = btn.querySelector(".api-path")?.textContent; try { return cardUrlPath && cardUrlPath.trim() === new URL(historyItem.url).pathname; } catch { return false; } }); if (endpointCard) { const cardId = endpointCard.closest(".card").id; const collapseTargetId = endpointCard.getAttribute("data-target"); if (collapseTargetId) { $(collapseTargetId).collapse("show"); setTimeout(() => { const urlInput = document.getElementById(`url-${cardId}`); if (urlInput) urlInput.value = historyItem.url; if (historyItem.payloadSummary && historyItem.payloadSummary.startsWith("{")) { try { const payloadObj = JSON.parse(historyItem.payloadSummary); const payloadTabContent = document.getElementById(`request-tab-content-${cardId}`); if (payloadTabContent) { const bodyPack = payloadTabContent.querySelector(".body-pack"); if (bodyPack) { Object.entries(payloadObj).forEach(([key, value]) => { const inputEl = bodyPack.querySelector(`input[placeholder="Enter ${key}"]`); if (inputEl) { inputEl.value = typeof value === "object" ? JSON.stringify(value) : value; } }); } } } catch (e) { console.warn("Could not parse payload from history for re-population", e); } } endpointCard.scrollIntoView({ behavior: "smooth", block: "center" }); showToast("Form populated from history."); }, 300); } } else { showToast("Could not find matching API card to re-populate."); } } function parsePayload(packer) { if (!packer) return; const result = {}; let isSet = false; const divs = Array.from(packer.children); divs.forEach((div2) => { const inputEl = div2.querySelector("input"); if (!inputEl) return; isSet = true; const key = div2.querySelector("span")?.textContent?.replace(":", ""); if (!key) return; if (inputEl.type === "file") { result[key] = inputEl.files[0]; return; } result[key.trim()] = inputEl.value; }); if (!isSet) return; return result; } function getInputValue(inputEl) { if (!inputEl) return; if (inputEl.type === "file") { return inputEl.files && inputEl.files.length > 0 ? inputEl.files[0] : undefined; } if (inputEl.type === "number") { return inputEl.value === "" ? undefined : Number(inputEl.value); } if (inputEl.type === "checkbox") return inputEl.checked; const val = inputEl.value; if (val === "true") return true; if (val === "false") return false; return val; } function parsePayloadData(bodyPackElement) { if (!bodyPackElement) return {}; function processNode(node) { if (node.matches(".form-group.data-input")) { const inputEl = node.querySelector('input:not([type="button"]), select, textarea'); return getInputValue(inputEl); } else if (node.dataset?.["obj"] === "true" && node.matches(".data-input")) { const objectData = {}; Array.from(node.children).forEach((childNode) => { if (childNode.matches(".form-group.data-input, .data-input")) { const key = childNode.querySelector("span[data-key-name]")?.dataset?.["keyName"] || childNode.dataset?.["holder"]; if (key) { objectData[key] = processNode(childNode); } } }); return objectData; } else if (node.dataset?.["arr"] === "true" && node.matches(".data-input")) { const arrayData = []; const arrayItemsContainer = node.querySelector(".array-cont"); if (arrayItemsContainer) { Array.from(arrayItemsContainer.children).filter((item) => item.matches(".array-item")).forEach((itemElement) => { const itemObject = {}; Array.from(itemElement.children).forEach((fieldNode) => { if (fieldNode.matches(".form-group.data-input, .data-input")) { const key = fieldNode.querySelector("span[data-key-name]")?.dataset?.["keyName"] || fieldNode.dataset?.["holder"]; if (key) { itemObject[key] = processNode(fieldNode); } } }); arrayData.push(itemObject); }); } else { const simpleArrayInput = node.querySelector('input[type="text"][placeholder*="comma-separated"]'); if (simpleArrayInput) { const val = getInputValue(simpleArrayInput); if (val && typeof val === "string" && val.trim() !== "") { arrayData.push(...val.split(",").map((s) => s.trim()).filter((s) => s)); } else if (val !== undefined && val !== "" && val !== null) { arrayData.push(val); } } } return arrayData; } return; } const assembledPayload = {}; Array.from(bodyPackElement.children).forEach((topLevelNode) => { if (topLevelNode.matches(".form-group.data-input, .data-input")) { const key = topLevelNode.querySelector("span[data-key-name]")?.dataset?.["keyName"] || topLevelNode.dataset?.["holder"]; if (key) { assembledPayload[key] = processNode(topLevelNode); } } }); return assembledPayload; } function parsePayloadStructure(apiSchema, cardId, isPayload = true) { if (!apiSchema) return; const packer = div({ className: "body-pack" }); function createInputField(key, valueSchema, _type, _holder, currentPath = "") { const fieldId = `${cardId}-field-${(currentPath + key).replace(/[^a-zA-Z0-9]/g, "_")}`; if (typeof valueSchema === "object" && !Array.isArray(valueSchema) && valueSchema !== null) { const nestedDiv = div({ className: "data-input", "data-obj": "true", "data-holder": key, style: { marginLeft: "15px", borderLeft: "2px solid var(--surface-3)", paddingLeft: "10px", marginBottom: "10px" } }, h5({ style: { fontSize: "0.9em", color: "var(--text-secondary)" } }, key + ": {object}")); Object.entries(valueSchema ?? {}).forEach(([nestedKey, nestedValue]) => { nestedDiv.appendChild(createInputField(nestedKey, nestedValue, "obj", key, currentPath + key + ".")); }); return nestedDiv; } else if (Array.isArray(valueSchema)) { const arrayDiv = div({ className: "data-input", "data-arr": "true", "data-holder": key, style: { marginLeft: "15px", borderLeft: "2px solid var(--surface-3)", paddingLeft: "10px", marginBottom: "10px" } }, h5({ style: { fontSize: "0.9em", color: "var(--text-secondary)" } }, key + ": [array]")); if (valueSchema.length > 0 && typeof valueSchema[0] === "object" && valueSchema[0] !== null) { const arrayContainer = div({ className: "array-cont" }); arrayDiv.appendChild(arrayContainer); let pos = 0; const addItem = () => { const itemDiv = div({ className: "array-item", "data-pos": pos, style: { border: "1px dashed var(--border-color)", padding: "10px", marginBottom: "5px" } }); Object.entries(valueSchema).forEach(([arrayKey, arrayValue]) => { itemDiv.appendChild(createInputField(arrayKey, arrayValue, "arr-obj", key, `${currentPath}${key}[${pos}].`)); }); arrayContainer.appendChild(itemDiv); pos += 1; }; addItem(); arrayDiv.appendChild(button("+ Add Item", { onclick: addItem, className: "btn btn-sm btn-outline-secondary", style: { fontSize: "0.8rem", marginTop: "5px" } })); } else { arrayDiv.appendChild(span({ "data-key-name": key, style: { marginRight: "5px" } }, key + ": ")); arrayDiv.appendChild(input({ type: valueSchema[0].split(":")[0], id: fieldId, placeholder: "Enter " + key + " (comma-separated values)", value: valueSchema[0].split(":")[1], className: "form-control form-control-sm" })); } return arrayDiv; } else { let [type, value] = typeof valueSchema === "string" ? valueSchema.split(":") : [valueSchema, null]; if (!isPayload) value = valueSchema; return div({ className: "form-group row data-input", style: { marginBottom: "0.5rem" } }, span({ htmlFor: fieldId, className: "col-sm-4 col-form-label col-form-label-sm", "data-key-name": key, style: { fontWeight: "normal" } }, key + ": "), div({ className: "col-sm-8" }, input({ type: /(file|number|string|date|datetime|time|email|url|tel|password|checkbox|radio|select|textarea|hidden|button|submit|reset|image|color|month|week|range)/.test(type) ? type : "text", id: fieldId, value: type !== "file" && value !== null ? value : null, placeholder: "Enter " + key, className: "form-control form-control-sm" }))); } } if (apiSchema && typeof apiSchema === "object") { Object.entries(apiSchema).forEach(([key, value]) => { packer.appendChild(createInputField(key, value, "root", "")); }); } else if (apiSchema) { packer.appendChild(span("Payload schema is not a valid object. Raw: " + escapeHtml(String(apiSchema)))); } else { packer.appendChild(span("No payload schema defined for this request.")); } return packer; } function parseApiDocumentation(apiDocString) { const requests = apiDocString.split("### break ###").map((request) => request.trim()).filter((a2) => a2 !== ""); return requests.map(parseRequest); } function parseRequest(requestString) { const lines = requestString.split("\\n").map((line) => line.trim()); const requestLine = lines[0].split(" "); const method = requestLine[0]; const url = requestLine[1]; const httpVersion = requestLine[2]; const headers = {}; for (let i = 1;i < lines.length; i++) { if (lines[i] === "") break; const [key, value] = lines[i].split(":").map((part) => part.trim()); headers[key.toLowerCase()] = value; } const payloadIndex = lines.indexOf("") + 1; let payload = payloadIndex !== 0 ? lines.slice(payloadIndex).join("\\n") : null; let title = ""; let description = ""; if (payload?.includes("#")) { if (payload.includes("#-JET-TITLE")) { title = payload.slice(payload.indexOf("#-JET-TITLE") + 12, payload.lastIndexOf("#-JET-TITLE")); } if (payload.includes("#-JET-DESCRIPTION")) { description = payload.slice(payload.indexOf("#-JET-DESCRIPTION") + 18, payload.lastIndexOf("#-JET-DESCRIPTION")); } payload = payload.split(` `).filter((line) => !line.startsWith("#")).join(` `); } return { method, url, httpVersion, headers, payload, title, description }; } var groupByFirstFeature = (apis) => { const results = {}; const out = []; for (let a2 = 0;a2 < apis.length; a2++) { const api = apis[a2]; try { const top_path = new URL(api?.url).pathname.split("/")[1] || "/"; if (results[top_path]) results[top_path].push(api); else results[top_path] = [api]; results[top_path].sort((x, y) => x.url.localeCompare(y.url)); } catch (e) { console.warn("Skipping API with invalid URL:", api); continue; } } for (const apilist in results) { out.push({ title: apilist, isGroup: true }); out.push(...results[apilist]); } return out; }; function showApiResponse(response, cardId, expectedStatusCode, expectedBodyContains, startTime) { const endTime = performance.now(); const requestTime = (endTime - startTime).toFixed(0); const responseSize = response.body ? new TextEncoder().encode(response.body).length : 0; const responseBodyTab = document.getElementById(`response-body-tab-${cardId}`); const responseHeadersTab = document.getElementById(`response-headers-tab-${cardId}`); const testResultsTab = document.getElementById(`test-results-tab-${cardId}`); const responseMetaInfo = document.getElementById(`response-meta-${cardId}`); if (!responseBodyTab || !responseHeadersTab || !testResultsTab || !responseMetaInfo) return; responseMetaInfo.innerHTML = ""; responseMetaInfo.appendChild(span({ className: `status-${response.status < 400 && !response.error ? "success" : "error"}` }, `Status: ${response.status || (response.error ? "Client Error" : "N/A")}`)); responseMetaInfo.appendChild(span(`Time: ${requestTime} ms`)); responseMetaInfo.appendChild(span(`Size: ${(responseSize / 1024).toFixed(2)} KB`)); responseBodyTab.innerHTML = ""; if (response.body) { responseBodyTab.appendChild(button({ className: "btn btn-sm btn-outline-secondary float-right mb-2", onclick: () => copyToClipboard(response.body, "Response Body") }, "Copy Body")); responseBodyTab.appendChild(div({ className: "code-container", style: { overflowX: "auto" }, innerHTML: syntaxHighlight(response.body) })); } else { responseBodyTab.appendChild(span(response.error || "No response body.")); } responseHeadersTab.innerHTML = ""; if (response.headers && typeof response.headers.forEach === "function") { const headersTable = table({ className: "table table-sm headers-table" }); const tHead = thead(tr(th("Header Name"), th("Header Value"))); const tBody = tbody(); response.headers.forEach((value, name) => { tBody.appendChild(tr(td(name), td(value))); }); headersTable.appendChild(tHead); headersTable.appendChild(tBody); responseHeadersTab.appendChild(headersTable); } else { responseHeadersTab.appendChild(span("No headers in response.")); } testResultsTab.innerHTML = ""; let allAssertionsPassed = true; if (expectedStatusCode) { const statusCode = response.status || (response.error ? 0 : 404); const statusPass = Number(expectedStatusCode) === statusCode; if (!statusPass) allAssertionsPassed = false; testResultsTab.appendChild(div({ className: `assertion-result ${statusPass ? "assertion-pass" : "assertion-fail"}` }, `Status Code: Expected ${expectedStatusCode}, Got ${statusCode}. (${statusPass ? "PASS" : "FAIL"})`)); } if (expectedBodyContains && response.body) { const bodyPass = response.body.includes(expectedBodyContains); if (!bodyPass) allAssertionsPassed = false; testResultsTab.appendChild(div({ className: `assertion-result ${bodyPass ? "assertion-pass" : "assertion-fail"}` }, `Body Contains "${expectedBodyContains}": ${bodyPass ? "Found (PASS)" : "Not Found (FAIL)"}`)); } if (!expectedStatusCode && !expectedBodyContains) { testResultsTab.appendChild(span("No assertions defined for this request.")); } document.getElementById(`response-container-tabs-${cardId}`).style.display = "block"; const tabToActivate = expectedStatusCode || expectedBodyContains ? `test-results-nav-${cardId}` : `response-body-nav-${cardId}`; $(`#${tabToActivate}`).tab("show"); } function createApiCard(request, i) { const payloadSchema = JSON.parse(request.payload?.includes("{") ? request.payload : "null"); const cardIdSuffix = request.method.toLowerCase() + request.url.replace(/[^a-zA-Z0-9]/g, "") + i; const cardId = `card-${cardIdSuffix}`; const collapseId = `collapse-${cardIdSuffix}`; const requestTabs = ul({ className: "nav nav-tabs", role: "tablist" }, li({ className: "nav-item" }, a({ className: "nav-link active", id: `request-nav-${cardId}`, "data-toggle": "tab", href: `#request-tab-content-${cardId}`, role: "tab" }, "Request")), li({ className: "nav-item" }, a({ className: "nav-link", id: `auth-nav-${cardId}`, "data-toggle": "tab", href: `#auth-tab-content-${cardId}`, role: "tab" }, "Global Auth")), li({ className: "nav-item" }, a({ className: "nav-link", id: `assertions-nav-${cardId}`, "data-toggle": "tab", href: `#assertions-tab-content-${cardId}`, role: "tab" }, "Assertions")), li({ className: "nav-item" }, a({ className: "nav-link", id: `curl-nav-${cardId}`, "data-toggle": "tab", href: `#curl-tab-content-${cardId}`, role: "tab" }, "cURL"))); const requestTabsContent = div({ className: "tab-content" }, div({ className: "tab-pane fade show active", id: `request-tab-content-${cardId}`, role: "tabpanel" }, div({ className: "form-group mt-3" }, label({ htmlFor: `url-${cardId}` }, "Request URL"), input({ className: "form-control url-input", id: `url-${cardId}`, value: request.url })), div({ className: "form-group" }, label({ htmlFor: `content-type-dropdown-${cardId}` }, "Content-Type"), selectEl({ id: `content-type-dropdown-${cardId}`, className: "form-control form-control-sm" }, option({ value: "application/json" }, "JSON"), option({ value: "multipart/form-data" }, "Form Data"), option({ value: "application/x-www-form-urlencoded" }, "Form URL Encoded"))), $if(payloadSchema, () => div({ className: "payload-section" }, strong("Payload:"), parsePayloadStructure(payloadSchema, cardId)))), div({ className: "tab-pane fade", id: `auth-tab-content-${cardId}`, role: "tabpanel" }, div({ className: "global-auth-section mt-3", id: `global-auth-preview-${cardId}` }, strong("Global Authentication Headers (Read-only):"))), div({ className: "tab-pane fade", id: `assertions-tab-content-${cardId}`, role: "tabpanel" }, div({ className: "form-group mt-3" }, label({ htmlFor: `expected-status-${cardId}` }, "Expected Status Code"), input({ type: "number", id: `expected-status-${cardId}`, placeholder: "e.g., 200", className: "form-control form-control-sm" })), div({ className: "form-group" }, label({ htmlFor: `expected-body-${cardId}` }, "Response Body Contains (Text)"), input({ type: "text", id: `expected-body-${cardId}`, placeholder: 'e.g., "success": true', className: "form-control form-control-sm" }))), div({ className: "tab-pane fade", id: `curl-tab-content-${cardId}`, role: "tabpanel" }, div({ className: "mt-3 curl-output", id: `curl-output-${cardId}` }, pre('Click "Generate cURL" after configuring request.')))); const responseSectionTabs = ul({ className: "nav nav-tabs mt-3", role: "tablist" }, li({ className: "nav-item" }, a({ className: "nav-link active", id: `response-body-nav-${cardId}`, "data-toggle": "tab", href: `#response-body-tab-${cardId}`, role: "tab" }, "Body")), li({ className: "nav-item" }, a({ className: "nav-link", id: `response-headers-nav-${cardId}`, "data-toggle": "tab", href: `#response-headers-tab-${cardId}`, role: "tab" }, "Headers")), li({ className: "nav-item" }, a({ className: "nav-link", id: `test-results-nav-${cardId}`, "data-toggle": "tab", href: `#test-results-tab-${cardId}`, role: "tab" }, "Test Results"))); const responseSectionTabsContent = div({ className: "tab-content" }, div({ className: "tab-pane fade show active", id: `response-body-tab-${cardId}`, role: "tabpanel", style: { padding: "10px", flexDirection: "column" } }), div({ className: "tab-pane fade", id: `response-headers-tab-${cardId}`, role: "tabpanel", style: { padding: "10px" } }), div({ className: "tab-pane fade", id: `test-results-tab-${cardId}`, role: "tabpanel", style: { padding: "10px" } })); const clearForm = () => { const requestTab = document.getElementById(`request-tab-content-${cardId}`); if (requestTab) { requestTab.querySelectorAll('input[type="text"], input[type="number"], input[type="file"], textarea').forEach((inp) => inp.value = ""); requestTab.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach((inp) => inp.checked = false); } document.getElementById(`expected-status-${cardId}`).value = ""; document.getElementById(`expected-body-${cardId}`).value = ""; document.getElementById(`response-meta-${cardId}`).innerHTML = ""; document.getElementById(`response-body-tab-${cardId}`).innerHTML = ""; document.getElementById(`response-headers-tab-${cardId}`).innerHTML = ""; document.getElementById(`test-results-tab-${cardId}`).innerHTML = ""; document.getElementById(`curl-output-${cardId}`).querySelector("pre").textContent = 'Click "Generate cURL" after configuring request.'; showToast("Form cleared."); }; const generateCurlAction = () => { const method = request.method.toUpperCase(); const url = document.getElementById(`url-${cardId}`)?.value?.trim(); const globalHeaders = parsePayload(document.getElementById("keys")) || {}; const specificHeaders = request.headers; const allHeaders = { ...specificHeaders, ...globalHeaders }; let curlCommand = `curl --location --request ${method} '${url}' \\ `; for (const key in allHeaders) { if (Object.hasOwnProperty.call(allHeaders, key)) { curlCommand += `--header '${key}: ${allHeaders[key]}' \\ `; } } const payloadData = parsePayloadData(document.getElementById(`request-tab-content-${cardId}`).querySelector(".body-pack")); const contentType = document.getElementById(`content-type-dropdown-${cardId}`)?.value || "application/json"; if (payloadData && Object.keys(payloadData).length > 0) { curlCommand += `--header 'Content-Type: ${contentType}' \\ `; if (contentType === "application/json") { curlCommand += `--data-raw '${JSON.stringify(payloadData)}'`; } else if (contentType === "multipart/form-data") { for (const key in payloadData) { curlCommand += `--form '${key}=${payloadData[key] instanceof File ? payloadData[key].name : JSON.stringify(payloadData[key])}' \\ `; } curlCommand = curlCommand.slice(0, -4); } else if (contentType === "application/x-www-form-urlencoded") { curlCommand += `--data-raw '${new URLSearchParams(payloadData).toString()}'`; } } else if (curlCommand.endsWith(" \\\n")) curlCommand = curlCommand; const curlOutputPre = document.getElementById(`curl-output-${cardId}`).querySelector("pre"); curlOutputPre.textContent = curlCommand; let copyBtn = document.getElementById(`copy-curl-${cardId}`); if (!copyBtn && curlOutputPre.parentNode) { copyBtn = button({ id: `copy-curl-${cardId}`, className: "btn btn-sm btn-outline-secondary mt-2", onclick: () => copyToClipboard(curlCommand, "cURL command") }, "Copy cURL"); curlOutputPre.parentNode.appendChild(copyBtn); } $(`#curl-nav-${cardId}`).tab("show"); }; const sendRequestAction = async () => { const startTime = performance.now(); const currentUrl = document.getElementById(`url-${cardId}`)?.value?.trim(); const globalAuthHeaders = parsePayload(document.getElementById("keys")) || {}; const requestSpecificHeaders = request.headers; const combinedHeaders = { ...requestSpecificHeaders, ...globalAuthHeaders }; const payloadData = parsePayloadData(document.getElementById(`request-tab-content-${cardId}`).querySelector(".body-pack")); const expectedStatusCode = document.getElementById(`expected-status-${cardId}`)?.value; const expectedBodyContent = document.getElementById(`expected-body-${cardId}`)?.value; const contentType = document.getElementById(`content-type-dropdown-${cardId}`)?.value || "application/json"; const responseContainerTabs = document.getElementById(`response-container-tabs-${cardId}`); responseContainerTabs.style.display = "block"; document.getElementById(`response-meta-${cardId}`).innerHTML = ""; document.getElementById(`response-body-tab-${cardId}`).innerHTML = ""; document.getElementById(`response-headers-tab-${cardId}`).innerHTML = ""; document.getElementById(`test-results-tab-${cardId}`).innerHTML = ""; document.getElementById(`response-body-tab-${cardId}`).appendChild(loading_svg()); const apiResponse = await testApi(request.method, currentUrl, combinedHeaders, payloadData, contentType); showApiResponse(apiResponse, cardId, Number(expectedStatusCode), expectedBodyContent, startTime); if (!apiResponse.error) { let payloadSummary = ""; if (payloadData && Object.keys(payloadData).length > 0) { payloadSummary = JSON.stringify(payloadData).substring(0, 70) + (JSON.stringify(payloadData).length > 70 ? "..." : ""); } saveToHistory(request.method, currentUrl, apiResponse.status, payloadSummary, (performance.now() - startTime).toFixed(0)); } }; return div({ className: "card", id: cardId }, div({ className: "card-header", id: `header-${cardId}` }, h5({ className: "mb-0" }, button({ className: "btn btn-link collapsed", type: "button", "data-toggle": "collapse", "data-target": `#${collapseId}`, "aria-expanded": "false", "aria-controls": collapseId, onclick: () => { const authPreview = document.getElementById(`global-auth-preview-${cardId}`); if (authPreview && authPreview.children.length <= 1) { if (Object.keys(request.headers).length > 0) { Object.entries(request.headers).forEach(([key, value]) => { authPreview.appendChild(div({ style: { fontSize: "0.85rem" } }, strong(key + ": "), span(value))); }); } else { authPreview.appendChild(span({ style: { fontSize: "0.85rem" } }, "No global authentication headers configured or found.")); } } } }, span(request.method, { className: "method " + request.method }), span({ className: "api-path" }, request.url ? new URL(request.url).pathname : "Invalid URL"), span({ className: "text-muted small ml-2", style: { fontWeight: "normal" } }, request.title || "")))), div({ id: collapseId, className: "collapse", "data-parent": "#api-endpoint-container" }, div({ className: "card-body" }, requestTabs, $if(request.description || request.title, div({ className: "tab-content", innerHTML: markdown((request.description || "#" + request.title)?.slice(1)?.replaceAll(` #`, ` `)) })), requestTabsContent, div({ className: "action-buttons mt-3 mb-3" }, button("Send Request", { className: "btn btn-primary", onclick: sendRequestAction }), button("Generate cURL", { className: "btn btn-secondary", onclick: generateCurlAction }), button("Clear Form", { className: "btn btn-outline-danger btn-sm", onclick: clearForm })), div({ id: `response-meta-${cardId}`, className: "response-meta" }), div({ id: `response-container-tabs-${cardId}`, style: { display: "none" } }, responseSectionTabs, responseSectionTabsContent)))); } function renderApiEndpoints() { const apiEndpointContainer = document.getElementById("api-endpoint-container"); const searchInput = document.getElementById("api-search-input"); if (!apiEndpointContainer || !searchInput) return; const searchTerm = searchInput.value.toLowerCase(); apiEndpointContainer.innerHTML = ""; const apiDocumentation = apiDocumentationRawTemplate.replaceAll("[--host--]", currentBaseUrl); const parsedApis = parseApiDocumentation(apiDocumentation); let visibleApis = parsedApis; if (searchTerm) { visibleApis = parsedApis.filter((api) => { const fullText = `${api.method} ${api.url} ${api.title || api.description || ""}`.toLowerCase(); return fullText.includes(searchTerm); }); } const groupedApis = groupByFirstFeature(visibleApis); if (groupedApis.length === 0 && searchTerm) { apiEndpointContainer.appendChild(div({ className: "alert alert-warning" }, "No endpoints match your search.")); return; } else if (groupedApis.length === 0) { apiEndpointContainer.appendChild(div({ className: "alert alert-info" }, "No API endpoints defined or an error occurred parsing them.")); return; } groupedApis.forEach((item, i) => { if (item.isGroup) { const groupTitle = h4({ className: "mt-3 mb-2 text-muted px-2", style: { fontSize: "1rem", borderBottom: "1px solid var(--surface-3)", paddingBottom: "5px" } }, item.title.toUpperCase()); apiEndpointContainer.appendChild(groupTitle); } else { apiEndpointContainer.appendChild(createApiCard(item, i)); } }); } async function testApi(method, url, headers = {}, body, contentType = "application/json") { if (contentType !== "multipart/form-data" && contentType !== "application/octet-stream") headers["Content-Type"] = contentType; else if (contentType === "multipart/form-data") { delete headers["Content-Type"]; } let response; try { const fetchOptions = { method, headers, signal: AbortSignal.timeout(30000), body }; if (method !== "GET" && method !== "HEAD" && body !== undefined) { if (contentType === "application/json") { fetchOptions.body = JSON.stringify(body); } else if (contentType === "multipart/form-data") { const formData = new FormData; if (body) { for (const key in body) formData.append(key, body[key]); } fetchOptions.body = formData; } else if (contentType === "application/x-www-form-urlencoded") { fetchOptions.body = new URLSearchParams(body).toString(); } else fetchOptions.body = body; } else { delete fetchOptions.body; } response = await fetch(url, fetchOptions); const responseBody = await response.text(); const responseHeaders = {}; for (const [key, value] of response.headers.entries()) { responseHeaders[key] = value; } return { status: response.status, headers: response.headers, body: responseBody }; } catch (error) { console.error("API Test Error:", error); return { error: error.message, status: 0, body: error.message, headers: new Headers }; } } document.addEventListener("DOMContentLoaded", () => { createEnvironmentSelector(); try { document.getElementById("keys")?.appendChild(parsePayloadStructure(JSON.parse(apiGlobalHeadersRaw), "global-headers", false)); } catch (e) { console.error("Failed to parse global headers JSON:", apiGlobalHeadersRaw, e); } renderApiEndpoints(); renderHistory(); const searchInput = document.getElementById("api-search-input"); if (searchInput) { searchInput.addEventListener("keyup", () => { clearTimeout(searchInput.searchTimeout); searchInput.searchTimeout = setTimeout(renderApiEndpoints, 300); }); } }); </script> </body> </html>