diff --git a/app/[slug]/page.tsx b/app/[slug]/page.tsx index f1d317e..d982949 100644 --- a/app/[slug]/page.tsx +++ b/app/[slug]/page.tsx @@ -351,7 +351,7 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { // Handle regular description text const descriptions = desc?.description || ""; - const splitDescriptions = descriptions.split(/(".*?")/); // Split quoted and unquoted text + const splitDescriptions = descriptions.split(/((['"]).*?\2)/); // Split quoted and unquoted text return (

{ {splitDescriptions.map( (text: any, subIndex: any) => { const isQuoted = - text.startsWith("") && text.endsWith(""); + typeof text === "string" && /^(['"]).*\1$/.test(text); const containsBetterBugs = text.includes("BetterBugs.io"); @@ -372,7 +372,9 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { - {parts[0]} + + {parts[0]} + { > BetterBugs.io - {parts[1]} + + {parts[1]} + ); } @@ -429,12 +433,12 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => {

)} {development_tools_user_agent_info?.example_string && ( -

+

{development_tools_user_agent_info?.example_string}

)} {development_tools_user_agent_info?.example_string_description && ( -

+

{development_tools_user_agent_info?.example_string_description}

)} @@ -471,16 +475,21 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => {

{development_tools_steps_guide?.guide_description - ?.split(/(".*?")/g) - ?.map((parts: any, i: any) => - parts?.startsWith("") && parts?.endsWith("") ? ( + ?.split(/((['"]).*?\2)/g) + ?.map((parts: any, i: any) => { + const isQuoted = + typeof parts === "string" && + /^(['"]).*\1$/.test(parts); + return isQuoted ? ( {parts} ) : ( - {parts} - ) - )} + + {parts} + + ); + })}

)} @@ -492,8 +501,8 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { const description2 = guide?.step_description2; // Split quoted and unquoted text - const parts = description?.split(/(".*?")/); - const desParts = description2?.split(/(".*?")/); + const parts = description?.split(/((['"]).*?\2)/); + const desParts = description2?.split(/((['"]).*?\2)/); return (
@@ -515,24 +524,31 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { {/* Step descriptions (below title) */} {description && (
- {parts?.map((part: any, i: any) => - part.startsWith("") && part.endsWith("") ? ( + {parts?.map((part: any, i: any) => { + const isQuoted = + typeof part === "string" && + /^(['"]).*\1$/.test(part); + return isQuoted ? ( {part} ) : ( - {part} - ) - )} + + {part} + + ); + })}
)} {/* Step Description2 on a New Line, Only If Present */} {description2 && desParts?.length > 0 && (
- {desParts?.map((part: any, i: any) => - part.startsWith("") && - part.endsWith("") ? ( + {desParts?.map((part: any, i: any) => { + const isQuoted = + typeof part === "string" && + /^(['"]).*\1$/.test(part); + return isQuoted ? ( { {part} ) : ( - part - ) - )} + + {part} + + ); + })}
)} @@ -558,10 +579,11 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { {p?.steps_points_description && (

{p?.steps_points_description - ?.split(/(".*?")/) + ?.split(/((['"]).*?\2)/) .map((part: string, i: number) => - part.startsWith("") && part.endsWith("") ? ( - + typeof part === "string" && + /^(['"]).*\1$/.test(part) ? ( + {part} ) : ( @@ -597,9 +619,10 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { {sub_p?.description && ( {sub_p?.description - ?.split(/(".*?")/) + ?.split(/((['"]).*?\2)/) .map((part: string, i: number) => - part.startsWith("") && part.endsWith("") ? ( + typeof part === "string" && + /^(['"]).*\1$/.test(part) ? ( {part} @@ -799,7 +822,7 @@ const Page = ({ params: { slug } }: { params: { slug: string } }) => { (desc: any, index: number) => { const descriptions = desc?.descriptions; const splitDescriptions = - descriptions.split(/(".*?")/); // Split quoted and unquoted text + descriptions.split(/((['"]).*?\2)/); // Split quoted and unquoted text return (

{ {splitDescriptions.map( (text: any, subIndex: any) => { const isQuoted = - text.startsWith("") && text.endsWith(""); + typeof text === "string" && + /^(['"]).*\1$/.test(text); return ( ; + serverIPAddress?: string; + connection?: string; + _error?: string; +}; + +type ParsedHar = { + entries: HarEntry[]; + pageTitle?: string; + creator?: string; + browser?: string; +}; + +type EntryType = "all" | "xhr" | "js" | "css" | "img" | "media" | "other" | "errors"; + +const isObject = (v: unknown): v is Record => typeof v === "object" && v !== null; + +const safeJsonParse = (text: string): { data: unknown; error: string } => { + try { + return { data: JSON.parse(text), error: "" }; + } catch { + return { data: null, error: "Invalid JSON. Please upload a valid .har/.json file." }; + } +}; + +const parseHarLike = (raw: unknown): { har: ParsedHar | null; error: string } => { + if (!raw) return { har: null, error: "No data found in file." }; + + // Standard HAR shape + if (isObject(raw) && isObject(raw.log) && Array.isArray(raw.log.entries)) { + const log = raw.log as any; + const entries = (log.entries as any[]).filter(Boolean) as HarEntry[]; + const pageTitle = + Array.isArray(log.pages) && log.pages[0]?.title ? String(log.pages[0].title) : undefined; + const creator = log.creator?.name ? String(log.creator.name) : undefined; + const browser = log.browser?.name ? String(log.browser.name) : undefined; + return { har: { entries, pageTitle, creator, browser }, error: "" }; + } + + // Some exports are just { entries: [...] } + if (isObject(raw) && Array.isArray((raw as any).entries)) { + return { har: { entries: (raw as any).entries as HarEntry[] }, error: "" }; + } + + // Or an array of entries + if (Array.isArray(raw)) { + return { har: { entries: raw as HarEntry[] }, error: "" }; + } + + return { har: null, error: "Unsupported HAR format. Expected { log: { entries: [...] } }." }; +}; + +const hostFromUrl = (url: string) => { + try { + return new URL(url).host; + } catch { + return ""; + } +}; + +const pathFromUrl = (url: string) => { + try { + const u = new URL(url); + return `${u.pathname}${u.search}`; + } catch { + return url; + } +}; + +const formatBytes = (n?: number) => { + if (!Number.isFinite(n)) return "—"; + const v = Number(n); + const units = ["B", "KB", "MB", "GB"]; + let i = 0; + let x = Math.abs(v); + while (x >= 1024 && i < units.length - 1) { + x /= 1024; + i += 1; + } + const sign = v < 0 ? "-" : ""; + return `${sign}${x.toFixed(i === 0 ? 0 : 2)} ${units[i]}`; +}; + +const normalizeHeaders = (h?: HarNameValue[]) => + Array.isArray(h) ? h.map((x) => ({ name: String(x.name ?? ""), value: String(x.value ?? "") })) : []; + +const guessEntryType = (en: HarEntry): Exclude => { + const mime = + String(en.response?.content?.mimeType ?? en.request?.postData?.mimeType ?? "").toLowerCase(); + const url = String(en.request?.url ?? "").toLowerCase(); + + if (mime.includes("javascript") || url.endsWith(".js") || url.includes(".js?")) return "js"; + if (mime.includes("css") || url.endsWith(".css") || url.includes(".css?")) return "css"; + if ( + mime.startsWith("image/") || + /\.(png|jpe?g|gif|webp|svg|ico)(\?|#|$)/.test(url) + ) + return "img"; + if ( + mime.startsWith("video/") || + mime.startsWith("audio/") || + /\.(mp4|webm|mp3|wav|m4a|mov)(\?|#|$)/.test(url) + ) + return "media"; + + // Heuristic: XHR/fetch often has JSON or is not a static file + if (mime.includes("json") || mime.includes("xml") || mime.includes("graphql")) return "xhr"; + return "other"; +}; + +const clampTiming = (n: unknown) => { + const v = Number(n); + if (!Number.isFinite(v) || v < 0) return 0; + return v; +}; + +const timingSegments = (en: HarEntry) => { + const t = en.timings ?? {}; + const dns = clampTiming(t.dns); + const connect = clampTiming(t.connect); + const ssl = clampTiming(t.ssl); + const wait = clampTiming(t.wait); + const receive = clampTiming(t.receive); + const send = clampTiming(t.send); + const blocked = clampTiming(t.blocked); + return [ + { key: "blocked", label: "Blocked", ms: blocked, color: "#6b7280" }, + { key: "dns", label: "DNS", ms: dns, color: "#60a5fa" }, + { key: "connect", label: "Connect", ms: connect, color: "#34d399" }, + { key: "ssl", label: "SSL", ms: ssl, color: "#c084fc" }, + { key: "send", label: "Send", ms: send, color: "#93c5fd" }, + { key: "wait", label: "Waiting", ms: wait, color: "#fbbf24" }, + { key: "receive", label: "Content Download", ms: receive, color: "#22c55e" }, + ].filter((s) => s.ms > 0); +}; + +const HarFileViewer = () => { + const [rawText, setRawText] = useState(""); + const [har, setHar] = useState(null); + const [error, setError] = useState(""); + const [activeId, setActiveId] = useState(-1); + const [search, setSearch] = useState(""); + const [typeFilter, setTypeFilter] = useState("all"); + + const fileInputRef = useRef(null); + const dropRef = useRef(null); + + const reset = () => { + setRawText(""); + setHar(null); + setError(""); + setActiveId(-1); + setSearch(""); + setTypeFilter("all"); + }; + + const loadText = (text: string) => { + setRawText(text); + const parsed = safeJsonParse(text); + if (parsed.error) { + setHar(null); + setError(parsed.error); + return; + } + const res = parseHarLike(parsed.data); + setHar(res.har); + setError(res.error); + setActiveId(-1); + }; + + const onPickFile = () => fileInputRef.current?.click(); + + const onFileChange: React.ChangeEventHandler = async (e) => { + const file = e.target.files?.[0]; + if (!file) return; + try { + const text = await file.text(); + loadText(text); + } finally { + e.target.value = ""; + } + }; + + const onDrop: React.DragEventHandler = async (e) => { + e.preventDefault(); + const file = e.dataTransfer.files?.[0]; + if (!file) return; + const text = await file.text(); + loadText(text); + }; + + const onDragOver: React.DragEventHandler = (e) => { + e.preventDefault(); + }; + + const entries = useMemo(() => har?.entries ?? [], [har]); + + const filtered = useMemo(() => { + const q = search.trim().toLowerCase(); + return entries + .map((en, idx) => ({ en, idx })) + .filter(({ en }) => { + const url = String(en.request?.url ?? ""); + const status = Number(en.response?.status ?? 0); + const host = hostFromUrl(url); + const path = pathFromUrl(url); + + const matchesText = + !q || + url.toLowerCase().includes(q) || + host.toLowerCase().includes(q) || + path.toLowerCase().includes(q) || + String(en.response?.statusText ?? "").toLowerCase().includes(q); + + const isError = status >= 400; + const type = guessEntryType(en); + const matchesType = + typeFilter === "all" + ? true + : typeFilter === "errors" + ? isError + : type === typeFilter; + + return matchesText && matchesType; + }); + }, [entries, search, typeFilter]); + + const summary = useMemo(() => { + const total = entries.length; + let success = 0; + let redirect = 0; + let err = 0; + let totalTime = 0; + for (const e of entries) { + const st = Number(e.response?.status ?? 0); + if (st >= 200 && st < 300) success += 1; + else if (st >= 300 && st < 400) redirect += 1; + else if (st >= 400) err += 1; + totalTime += Number(e.time ?? 0) || 0; + } + const avgTime = total ? totalTime / total : 0; + return { total, success, redirect, err, avgTime }; + }, [entries]); + + const timelineMaxMs = useMemo(() => { + let max = 0; + for (const { en } of filtered) { + const t = Number(en.time ?? 0) || 0; + if (t > max) max = t; + } + return Math.max(100, Math.ceil(max / 100) * 100); + }, [filtered]); + + const copy = async (text: string) => { + try { + await navigator.clipboard.writeText(text); + } catch { + // ignore + } + }; + + const downloadJson = () => { + if (!rawText) return; + const blob = new Blob([rawText], { type: "application/json;charset=utf-8" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "network.har.json"; + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); + }; + + const formatJson = (v: any) => { + try { + return JSON.stringify(v, null, 2); + } catch { + return String(v ?? ""); + } + }; + + return ( +

+
+
+
+
+
+
+
+
Drop your .har or .json file here
+
+ Runs locally in your browser — nothing is uploaded. +
+
+
+ + + + +
+
+ {error &&
{error}
} + {har && ( +
+ Loaded {summary.total} entries ·{" "} + {summary.success} success ·{" "} + {summary.redirect} redirects ·{" "} + {summary.err} errors · avg{" "} + {Math.round(summary.avgTime)}ms + {har.pageTitle ? ( + <> + {" "} + · page {har.pageTitle} + + ) : null} + {har.creator ? ( + <> + {" "} + · creator {har.creator} + + ) : null} + {har.browser ? ( + <> + {" "} + · browser {har.browser} + + ) : null} +
+ )} +
+ + {/* Search + type chips */} +
+ setSearch(e.target.value)} + placeholder="Search in URL, headers, requests, and responses…" + className="w-full bg-black/40 border border-[#222222] rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary/60" + aria-label="Search entries" + /> + +
+ {( + [ + ["all", "All"], + ["xhr", "XHR"], + ["js", "JS"], + ["css", "CSS"], + ["img", "Img"], + ["media", "Media"], + ["other", "Other"], + ["errors", "Errors"], + ] as const + ).map(([key, label]) => ( + + ))} + +
+ Showing {filtered.length} /{" "} + {entries.length} +
+
+
+ + {/* Table + details */} +
+ {/* Header */} +
+
Status
+
Type
+
Request
+
Timeline range: {timelineMaxMs}ms
+
+ + {/* Rows */} +
+ {filtered.length === 0 ? ( +
No matching entries.
+ ) : ( +
    + {filtered.slice(0, 2000).map(({ en, idx }) => { + const method = String(en.request?.method ?? "").toUpperCase() || "—"; + const url = String(en.request?.url ?? ""); + const status = Number(en.response?.status ?? 0) || 0; + const totalMs = Math.max(0, Number(en.time ?? 0) || 0); + const selected = idx === activeId; + const statusColor = + status >= 200 && status < 300 + ? "text-green-300" + : status >= 300 && status < 400 + ? "text-yellow-300" + : status >= 400 + ? "text-red-300" + : "text-white/60"; + + const typ = guessEntryType(en); + const typeLabel = + typ === "xhr" ? "XHR" : typ === "js" ? "JS" : typ === "css" ? "CSS" : typ === "img" ? "Img" : typ === "media" ? "Media" : "Other"; + + const segs = timingSegments(en); + const segTotal = segs.reduce((a, s) => a + s.ms, 0) || totalMs || 1; + const max = timelineMaxMs || 1; + const barW = Math.max(2, Math.min(100, (totalMs / max) * 100)); + + const reqHeaders = normalizeHeaders(en.request?.headers); + const resHeaders = normalizeHeaders(en.response?.headers); + + return ( +
  • + + + {selected && ( +
    +
    +
    +
    + {method} {pathFromUrl(url)} +
    +
    + {hostFromUrl(url) || "—"} + {" · "}Status {status || "—"} + {" · "}Total {Math.round(totalMs)}ms +
    + +
    +
    +
    Type
    +
    {typeLabel.toUpperCase()}
    +
    +
    +
    Started
    +
    + {en.startedDateTime ? new Date(en.startedDateTime).toLocaleString() : "—"} +
    +
    +
    +
    MIME
    +
    + {en.response?.content?.mimeType ?? "—"} +
    +
    +
    +
    Size
    +
    + {formatBytes(Number(en.response?.content?.size ?? en.response?.bodySize ?? 0) || 0)} +
    +
    +
    + +
    +
    Timing breakdown
    +
    + {timingSegments(en).length === 0 ? ( +
    + ) : ( + timingSegments(en).map((s) => ( +
    + + {s.label} + {Math.round(s.ms)}ms +
    + )) + )} +
    +
    +
    + +
    +
    + +
    + +
    + + Response headers + +
    + {resHeaders.length === 0 ? ( +
    + ) : ( +
    +                                            {resHeaders.map((h) => `${h.name}: ${h.value}`).join("\n")}
    +                                          
    + )} +
    +
    + +
    + + Response content + +
    +
    + MIME:{" "} + {en.response?.content?.mimeType ?? "—"} + {" · "} + Encoding:{" "} + {en.response?.content?.encoding ?? "—"} +
    +
    +                                          {en.response?.content?.text ? String(en.response.content.text) : "—"}
    +                                        
    +
    +
    + + {reqHeaders.length > 0 && ( +
    + + Request headers + +
    +
    +                                            {reqHeaders.map((h) => `${h.name}: ${h.value}`).join("\n")}
    +                                          
    +
    +
    + )} +
    +
    +
    + )} +
  • + ); + })} +
+ )} +
+
+
+
+
+
+
+ ); +}; + +export default HarFileViewer; + diff --git a/app/components/developmentToolsComponent/htmlViewer.tsx b/app/components/developmentToolsComponent/htmlViewer.tsx index dba28d2..10f552f 100644 --- a/app/components/developmentToolsComponent/htmlViewer.tsx +++ b/app/components/developmentToolsComponent/htmlViewer.tsx @@ -1,22 +1,58 @@ "use client"; -import React, { useState } from "react"; + +import React, { useRef, useState } from "react"; +import DevelopmentToolsStyles from "../../developmentToolsStyles.module.scss"; + +const DEFAULT_HTML = + "

Hello, HTML Viewer

\n

Edit the HTML to see updates here.

"; +const DEFAULT_CSS = + "body{font-family:system-ui;padding:16px}\niframe, img{max-width:100%}"; +const DEFAULT_JS = "console.log('Viewer ready');"; const HtmlViewer: React.FC = () => { const [activeTab, setActiveTab] = useState<"html" | "css" | "js">("html"); - const [html, setHtml] = useState("

Hello, Html Viewer

\n

Edit the HTML to see updates here.

"); - const [css, setCss] = useState("body{font-family:system-ui;padding:16px}\niframe, img{max-width:100%}"); - const [js, setJs] = useState("console.log('Viewer ready');"); + const [html, setHtml] = useState(DEFAULT_HTML); + const [css, setCss] = useState(DEFAULT_CSS); + const [js, setJs] = useState(DEFAULT_JS); + + const fileInputRef = useRef(null); + + const editorValue = + activeTab === "html" ? html : activeTab === "css" ? css : js; + const setEditorValue = + activeTab === "html" ? setHtml : activeTab === "css" ? setCss : setJs; + + const tabLabel = + activeTab === "html" + ? "HTML" + : activeTab === "css" + ? "CSS" + : "JavaScript"; + + const fileAccept = + activeTab === "html" ? ".html,.htm,.txt,text/html" : "text/plain,.css,.js"; + + const buildDocument = () => { + return `\nPreview${html}', + example_string_description: + 'If you preview untrusted SVGs, always treat them like HTML: sanitize before rendering. This SVG Viewer removes common unsafe elements before previewing.', + info_items: [ + { + part: 'Scripts:', + description: 'Script tags can run code in some contexts and should be removed.', + }, + { + part: 'Event handlers:', + description: 'Attributes like onload/onclick can execute JavaScript and should be stripped.', + }, + { + part: 'foreignObject:', + description: + 'foreignObject can embed HTML inside SVG, which increases XSS risk when rendering untrusted content.', + }, + ], + }, + development_tools_what: { + about_title: 'What does the SVG Viewer do?', + what_description: [ + { + descriptions: + 'The SVG Viewer lets you paste SVG markup or upload an SVG file and instantly see a live preview in your browser.', + }, + { + descriptions: + 'It detects and displays common SVG attributes like width, height, and viewBox to help you troubleshoot sizing, alignment, and cropping issues.', + }, + { + descriptions: + 'For safer previews, the tool sanitizes the SVG before rendering by removing scripts and foreignObject content and stripping inline event handlers.', + }, + ], + }, + development_tools_steps_guide: { + guide_title: 'How to use the SVG Viewer?', + guide_description: 'Follow these steps:', + steps: [ + { + step_key: 'Step 1:', + step_title: 'Paste or upload:', + step_description: + 'Paste SVG markup into the editor or upload an .svg file to load its content.', + }, + { + step_key: 'Step 2:', + step_title: 'Preview:', + step_description: + 'See the rendered SVG immediately in the preview panel (scripts and foreignObject are removed).', + }, + { + step_key: 'Step 3:', + step_title: 'Inspect attributes:', + step_description: + 'Copy basic info such as width, height, and viewBox to debug scaling issues.', + }, + { + step_key: 'Step 4:', + step_title: 'Copy or download:', + step_description: + 'Copy the sanitized SVG markup or download it as a .svg file.', + }, + ], + }, + development_tools_how_use: { + how_use_title: 'Common use cases for SVG Viewer', + how_use_description: 'Popular reasons to use this tool:', + point: [ + { + title: 'Debugging scaling and alignment', + description: + 'Quickly check viewBox/size attributes when an SVG looks stretched, clipped, or misaligned in a UI.', + }, + { + title: 'Previewing icons from tickets/PRs', + description: + 'Paste SVG markup shared in chats or issues to confirm it renders correctly before merging.', + }, + { + title: 'Sanitizing for sharing', + description: + 'Remove scripts and foreignObject content from untrusted SVGs before sharing them with teammates.', + }, + { + title: 'Asset handoff', + description: + 'Download a cleaned SVG file after minor tweaks or after confirming the exported asset looks right.', + }, + ], + }, + development_tools_Comparison: { + title: 'SVG Viewer vs SVG Converter: which one should you use?', + description: [ + { + desc: 'Use SVG Viewer when you want to preview SVG markup, inspect basic attributes, and verify how an icon renders.', + }, + { + desc: 'Use SVG to React/CSS Utility when you want code output such as React components, CSS Data URIs, or CSS masks.', + }, + ], + }, + development_tool_example: { + example_title: 'SVG Viewer example', + example_description: + 'Here is a simple SVG you can paste into the tool to preview. It includes a viewBox so it scales correctly.', + example_input: { + title: 'Example SVG markup', + json_data: + '\\n \\n \\n \\n \\n', + }, + example_outputs: { + intro: 'After pasting, you can copy the detected attributes and the sanitized SVG output.', + outputs: [ + { + mode: 'Detected attributes', + title: 'What you should see', + content: + '{\\n "width": "120",\\n "height": "120",\\n "viewBox": "0 0 120 120"\\n}', + }, + { + mode: 'Sanitized SVG (copy)', + title: 'What the tool provides', + content: + 'A cleaned SVG string suitable for previewing and sharing (scripts/foreignObject removed, inline on* handlers stripped).', + }, + ], + }, + }, + meta_data: { + meta_title: 'SVG Viewer Online – Preview & Inspect SVG Markup', + meta_description: + 'Preview SVG markup instantly. Upload or paste SVG, inspect width/height/viewBox, copy sanitized SVG, and download .svg files.', + og_title: 'SVG Viewer – Free Online Tool', + og_description: + 'Paste or upload SVG markup to preview it safely, inspect attributes, and copy/download the sanitized SVG output.', + og_image: '/images/og-images/Cover.png', + }, + }, + [`har-file-viewer`]: { + hero_section: { + title: 'HAR File Viewer', + description: + 'Upload a HAR (HTTP Archive) file and inspect network requests, responses, headers, payloads, and timings in a clean viewer with search, type filters, and an inline waterfall timeline.', + }, + development_tools_list: [ + { tool: 'URL Parser & Query String Editor', url: PATHS.URL_PARSER }, + { tool: 'JSON Prettifier', url: PATHS.JSON_PRETTIFIER }, + { tool: 'HTML Viewer', url: PATHS.HTML_VIEWER }, + { tool: 'Curl to Code Converter', url: PATHS.CURL_TO_CODE_CONVERTER }, + ], + development_tools_about_details: { + about_title: 'What is a HAR File Viewer?', + about_description: [ + { + description: + 'A HAR (HTTP Archive) file is a JSON log exported from browser DevTools that contains every network request made during a browsing session.', + }, + { + description: + 'This HAR File Viewer helps you debug slow page loads, failed requests, redirects, and API errors by letting you search and inspect entries (URLs, status codes, headers, request payloads, response bodies, and timing breakdowns).', + }, + ], + }, + development_tools_user_agent_info: { + info_title: 'Privacy & security notes for HAR files', + intro_text: + 'HAR files can contain sensitive data (cookies, authorization headers, query params, and request/response bodies). Treat a HAR like a password: share only with trusted people and redact secrets before uploading to any third‑party service.', + example_string: + 'Authorization: Bearer \\nCookie: session=\\nPOST /login { "email": "user@company.com", "password": "••••••" }', + example_string_description: + 'This viewer runs locally in your browser and does not upload your HAR to a server. Still, consider removing tokens and personal data before sharing the file externally.', + info_items: [ + { + part: 'May include secrets:', + description: + 'Cookies, API keys, bearer tokens, and personal data can appear in headers, URLs, and bodies.', + }, + { + part: 'Redact before sharing:', + description: + 'Replace tokens and credentials with placeholders before attaching HAR files to tickets.', + }, + { + part: 'Local processing:', + description: + 'Your file is parsed and displayed in the browser; the tool does not send it to an external API.', + }, + ], + }, + development_tools_what: { + about_title: 'What can you do with this HAR viewer?', + what_description: [ + { + descriptions: + 'Upload a .har or .json file and instantly browse the captured requests in a searchable list.', + }, + { + descriptions: + 'Use quick type filters like "XHR", "JS", "CSS", "Img", "Media", and "Errors" to narrow down what you’re investigating.', + }, + { + descriptions: + 'Click any row to expand details directly below it, including headers, response content, and a timing breakdown.', + }, + { + descriptions: + 'Use the waterfall timeline to visually compare which requests are slow and which stage (DNS/connect/wait/download) is taking the time.', + }, + ], + }, + development_tools_steps_guide: { + guide_title: 'How to use the HAR File Viewer', + guide_description: 'Simple steps:', + steps: [ + { + step_key: 'Step 1:', + step_title: 'Export a HAR file:', + step_description: + 'Open browser DevTools → Network tab → reproduce the issue → export/save HAR (keep “Preserve log” enabled if needed).', + }, + { + step_key: 'Step 2:', + step_title: 'Upload or drop the file:', + step_description: + 'Drag and drop your .har/.json file into the viewer (or use Upload).', + }, + { + step_key: 'Step 3:', + step_title: 'Search & filter:', + step_description: + 'Use the search box and type chips (XHR/JS/CSS/Img/Media/Errors) to narrow down to a specific request.', + }, + { + step_key: 'Step 4:', + step_title: 'Inspect details:', + step_description: + 'Click a request row to expand its details below and review response headers/content and timing breakdown.', + }, + ], + }, + development_tools_how_use: { + how_use_title: 'Common use cases for HAR files', + how_use_description: 'Teams use HAR files for:', + point: [ + { + title: 'Debugging failed API requests', + description: + 'Find 4xx/5xx responses quickly and inspect headers and response bodies for error messages.', + }, + { + title: 'Performance analysis', + description: + 'Compare request durations, spot slow endpoints, and review timing breakdowns to identify bottlenecks.', + }, + { + title: 'Redirect and caching issues', + description: + 'Track 3xx chains and review cache-related headers to diagnose unexpected behavior.', + }, + { + title: 'Sharing reproducible evidence', + description: + 'Attach a HAR to tickets so engineers can inspect the exact network activity behind a bug.', + }, + ], + }, + development_tools_Comparison: { + title: 'HAR File Viewer vs DevTools Network tab', + description: [ + { + desc: 'Use DevTools Network when you can reproduce the issue live and want real-time throttling, replay, and debugging.', + }, + { + desc: 'Use a HAR viewer when you’re analyzing a captured session from a teammate/customer and need to search, filter, and share findings quickly.', + }, + ], + }, + development_tool_example: { + example_title: 'Example: what to look for in a HAR', + example_description: + 'After uploading, use the Errors filter to find failing requests and expand one row to inspect details.', + example_outputs: { + intro: 'Typical workflow:', + outputs: [ + { + mode: 'Filter', + title: 'Show errors only', + content: 'Click the "Errors" chip and search for your API host or endpoint path.', + }, + { + mode: 'Inspect', + title: 'Open the failing request', + content: + 'Expand the row and check response headers/content, then review request headers to confirm auth/cookies were sent.', + }, + { + mode: 'Timings', + title: 'Review performance', + content: + 'Use the waterfall bar and timing breakdown to identify whether the time is spent on DNS/connect/wait/download.', + }, + ], + }, + }, + meta_data: { + meta_title: 'HAR File Viewer Online – Analyze HTTP Archive Files', + meta_description: + 'Upload and analyze HAR files online. Inspect requests, responses, headers, payloads, status codes, redirects, and timings to debug network issues faster.', + og_title: 'HAR File Viewer – Free Online Tool', + og_description: + 'View and analyze HAR (HTTP Archive) files. Search entries, inspect headers/payloads/responses, and review timings for performance debugging.', + og_image: '/images/og-images/Cover.png', + }, + }, [`what-is-my-local-ip-address`]: { hero_section: { title: 'What Is My Local IP Address',