@jaredwray/mockhttp 1.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -7
- package/dist/index.d.mts +90 -11
- package/dist/index.mjs +202 -240
- package/package.json +17 -13
- package/site/dist/api/index.html +17983 -0
- package/site/dist/api/swagger.json +5487 -0
- package/site/dist/changelog/index.html +767 -0
- package/site/dist/changelog/page/2/index.html +578 -0
- package/site/dist/changelog/v0-1-0/index.html +453 -0
- package/site/dist/changelog/v0-2-0/index.html +437 -0
- package/site/dist/changelog/v0-2-1/index.html +438 -0
- package/site/dist/changelog/v0-2-2/index.html +436 -0
- package/site/dist/changelog/v0-2-3/index.html +440 -0
- package/site/dist/changelog/v0-3-0/index.html +443 -0
- package/site/dist/changelog/v0-4-0/index.html +437 -0
- package/site/dist/changelog/v0-4-1/index.html +441 -0
- package/site/dist/changelog/v0-4-2/index.html +446 -0
- package/site/dist/changelog/v0-4-3/index.html +437 -0
- package/site/dist/changelog/v0-5-0/index.html +443 -0
- package/site/dist/changelog/v0-6-0/index.html +444 -0
- package/site/dist/changelog/v0-6-1/index.html +441 -0
- package/site/dist/changelog/v0-7-0/index.html +445 -0
- package/site/dist/changelog/v0-8-0/index.html +440 -0
- package/site/dist/changelog/v0-9-0/index.html +437 -0
- package/site/dist/changelog/v1-0-0/index.html +446 -0
- package/site/dist/changelog/v1-0-1/index.html +437 -0
- package/site/dist/changelog/v1-1-0/index.html +445 -0
- package/site/dist/changelog/v1-2-0/index.html +443 -0
- package/site/dist/changelog/v1-3-0/index.html +448 -0
- package/site/dist/changelog/v1-4-0/index.html +445 -0
- package/site/dist/changelog/v1-5-0/index.html +439 -0
- package/site/dist/changelog/v1-5-1/index.html +443 -0
- package/site/dist/changelog/v1-6-0/index.html +467 -0
- package/site/dist/changelog/v1-7-0/index.html +462 -0
- package/site/dist/changelog/v1-8-0/index.html +481 -0
- package/site/dist/changelog/v1-8-1/index.html +457 -0
- package/site/dist/changelog/v1-9-0/index.html +495 -0
- package/site/dist/changelog/v2-0-0/index.html +500 -0
- package/site/dist/changelog-latest.json +269 -0
- package/site/dist/changelog.json +399 -0
- package/site/dist/css/api.css +1113 -0
- package/site/dist/css/highlight/highlight.min.js +1433 -0
- package/site/dist/css/highlight/styles/base16/docula.css +123 -0
- package/site/dist/css/home.css +133 -0
- package/site/dist/css/styles.css +1227 -0
- package/site/dist/css/variables.css +32 -0
- package/site/dist/docs/bins/index.html +942 -0
- package/site/dist/docs/configuration/index.html +771 -0
- package/site/dist/docs/hosted/index.html +544 -0
- package/site/dist/docs/https/index.html +758 -0
- package/site/dist/docs/index.html +601 -0
- package/site/dist/docs/library/index.html +780 -0
- package/site/dist/docs/taps/index.html +708 -0
- package/site/dist/favicon.svg +14 -0
- package/site/dist/feed.xml +1 -0
- package/site/dist/index.html +601 -0
- package/site/dist/js/api.js +363 -0
- package/site/dist/js/search.js +436 -0
- package/site/dist/llms-full.txt +7415 -0
- package/site/dist/llms.txt +40 -0
- package/site/dist/logo.svg +21 -0
- package/site/dist/robots.txt +2 -0
- package/site/dist/search-index.json +1 -0
- package/site/dist/sitemap.xml +1 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 73.01 55.68">
|
|
3
|
+
<!-- Generator: Adobe Illustrator 29.1.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 142) -->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>
|
|
6
|
+
.st0 {
|
|
7
|
+
fill: #231f20;
|
|
8
|
+
}
|
|
9
|
+
</style>
|
|
10
|
+
</defs>
|
|
11
|
+
<path class="st0" d="M19.83,43.94c-2.09-.04-5.87-.9-5.87-5.18,0-2.59.22-4.64.22-6.91,0-1.94-.72-3.56-4.28-3.82v-1.22c3.56-.22,4.28-1.48,4.28-3.78s-.22-3.74-.22-6.05c0-4.28,3.64-5.26,5.87-5.36l.04,1.04c-2.81.5-3.31,2.09-3.31,3.89,0,2.3.25,3.64.25,5.87,0,2.59-1.84,4.46-4.07,4.93v.14c2.12.5,4.07,2.12,4.07,4.97,0,2.34-.25,4.25-.25,6.44,0,1.94.61,3.49,3.31,4l-.04,1.04Z"/>
|
|
12
|
+
<path class="st0" d="M42.4,37.5v-1.01c2.12-.22,2.38-.36,2.38-2.88v-6.01c0-2.56-.86-4.18-3.1-4.18-1.37,0-2.63.76-4,1.87.04.43.11.86.11,1.55v6.98c0,2.27.32,2.45,2.3,2.66v1.01h-7.7v-1.01c2.23-.22,2.56-.36,2.56-2.74v-6.19c0-2.66-.83-4.14-3.02-4.14-1.44,0-2.84.97-3.96,1.87v8.46c0,2.38.25,2.52,2.3,2.74v1.01h-7.74v-1.01c2.38-.22,2.59-.36,2.59-2.74v-7.45c0-2.3-.14-2.45-2.16-2.81v-.9c1.69-.25,3.38-.72,5-1.44v2.92c.76-.58,1.51-1.19,2.66-1.87.9-.58,1.69-.94,2.84-.94,1.73,0,3.2,1.08,3.92,2.95,1.01-.79,1.94-1.4,2.84-2.02.79-.5,1.8-.94,2.84-.94,2.81,0,4.54,2.02,4.54,5.54v6.95c0,2.41.22,2.52,2.27,2.74v1.01h-7.49Z"/>
|
|
13
|
+
<path class="st0" d="M52.48,11.62c2.12,0,5.9.9,5.9,5.15,0,2.63-.25,4.07-.25,6.34,0,1.91.76,3.6,4.32,3.78v1.22c-3.53.25-4.32,1.51-4.32,3.82s.25,4.32.25,6.62c0,4.28-3.71,5.36-5.9,5.4v-1.04c2.77-.5,3.31-2.05,3.31-3.92,0-2.3-.29-4.21-.29-6.44,0-2.63,1.87-4.46,4.07-4.97v-.14c-2.12-.47-4.07-2.34-4.07-4.93,0-2.09.29-3.67.29-5.87,0-1.94-.61-3.46-3.31-3.96v-1.04Z"/>
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>MockHTTP</title><link>https://mockhttp.org/</link><description>HTTP mock server and httpbin replacement for API testing. Run it at mockhttp.org, in Docker, or in Node.js.</description><lastBuildDate>Fri, 18 Sep 2026 22:54:19 GMT</lastBuildDate><atom:link href="https://mockhttp.org/feed.xml" rel="self" type="application/rss+xml" /><item><title>Getting Started</title><link>https://mockhttp.org/docs/</link><guid isPermaLink="true">https://mockhttp.org/docs/</guid><description>tests GitHub license codecov npm npm Docker Pulls mockhttp.org A simple HTTP server for mocking responses in tests. Inspired by httpbin and built with Node.js and Fastify. Run it at mockhttp.org, with Docker (jaredwray/mockhttp), or in Node...</description></item><item><title>HTTPS and HTTP/2</title><link>https://mockhttp.org/docs/https/</link><guid isPermaLink="true">https://mockhttp.org/docs/https/</guid><description>MockHttp supports HTTPS with auto-generated self-signed certificates or your own custom certificates. No external dependencies are required — certificate generation uses only Node.js built-in crypto. The simplest way to enable HTTPS is to p...</description></item><item><title>Taps</title><link>https://mockhttp.org/docs/taps/</link><guid isPermaLink="true">https://mockhttp.org/docs/taps/</guid><description>The injection/tap feature allows you to "tap into" the request flow and inject custom responses for specific requests. This is particularly useful for: Offline testing - Mock external API responses without network access Testing edge cases...</description></item><item><title>Bins</title><link>https://mockhttp.org/docs/bins/</link><guid isPermaLink="true">https://mockhttp.org/docs/bins/</guid><description>Bins are ephemeral URL endpoints that capture incoming HTTP requests so you can inspect them later. They're the inverse of Taps: instead of injecting a response, they record everything they receive. Useful for: Debugging webhooks — point St...</description></item><item><title>Configuration</title><link>https://mockhttp.org/docs/configuration/</link><guid isPermaLink="true">https://mockhttp.org/docs/configuration/</guid><description>MockHttp supports rate limiting using @fastify/rate-limit. Rate limiting is enabled by default at 1000 requests per minute with localhost (127.0.0.1 and ::1) excluded from rate limiting. By default, MockHttp applies the following rate limit...</description></item><item><title>Library API</title><link>https://mockhttp.org/docs/library/</link><guid isPermaLink="true">https://mockhttp.org/docs/library/</guid><description>Parameters: options? (MockHttpOptions): port?: number - The port to listen on (default: 3000) host?: string - The host to listen on (default: '0.0.0.0') autoDetectPort?: boolean - Auto-detect next available port if in use (default: true) he...</description></item><item><title>Hosted Service</title><link>https://mockhttp.org/docs/hosted/</link><guid isPermaLink="true">https://mockhttp.org/docs/hosted/</guid><description>mockhttp.org is a free hosted instance of this codebase for testing. It runs on a Cloudflare Worker (no containers): documentation is served from Workers Assets, and mock APIs such as /get and /post run in the Worker. The service is globall...</description></item></channel></rss>
|
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<meta name="description" content="HTTP mock server and httpbin replacement for API testing. Run it at mockhttp.org, in Docker, or in Node.js.">
|
|
8
|
+
<link rel="stylesheet" href="/css/variables.css">
|
|
9
|
+
<link rel="stylesheet" href="/css/styles.css">
|
|
10
|
+
<link rel="stylesheet" href="/css/highlight/styles/base16/docula.css">
|
|
11
|
+
<link rel="icon" href="/logo.svg">
|
|
12
|
+
<script>
|
|
13
|
+
(function(){
|
|
14
|
+
window.__doculaThemeKey = 'docula:theme:' + ('https://mockhttp.org').replace(/^https?:\/\//, '');
|
|
15
|
+
var mode = localStorage.getItem(window.__doculaThemeKey) || 'light';
|
|
16
|
+
var resolved = mode === 'system'
|
|
17
|
+
? (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark')
|
|
18
|
+
: mode;
|
|
19
|
+
if (resolved === 'light') document.documentElement.setAttribute('data-theme', 'light');
|
|
20
|
+
try {
|
|
21
|
+
var authState = JSON.parse(localStorage.getItem('docula-auth-state'));
|
|
22
|
+
if (authState) {
|
|
23
|
+
window.__doculaAuth = authState;
|
|
24
|
+
if (authState.loggedIn) {
|
|
25
|
+
document.documentElement.classList.add('docula-auth-logged-in');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
// Ignore localStorage parsing errors
|
|
30
|
+
}
|
|
31
|
+
if (!window.__doculaAuth) {
|
|
32
|
+
window.__doculaAuth = { loggedIn: false, displayName: null };
|
|
33
|
+
}
|
|
34
|
+
})();
|
|
35
|
+
</script>
|
|
36
|
+
<title>MockHTTP - Getting Started</title>
|
|
37
|
+
<script type="application/ld+json">
|
|
38
|
+
{"@context":"https://schema.org","@type":"TechArticle","headline":"Getting Started","description":"","url":"https://mockhttp.org/","publisher":{"@type":"Organization","name":"MockHTTP"},"dateModified":"2026-09-18","keywords":[]}
|
|
39
|
+
</script>
|
|
40
|
+
</head>
|
|
41
|
+
|
|
42
|
+
<body>
|
|
43
|
+
<header class="header">
|
|
44
|
+
<div class="header-top">
|
|
45
|
+
<div class="header-top__content">
|
|
46
|
+
<button class="mobile-menu-toggle" id="mobile-menu-toggle" aria-label="Toggle navigation menu">
|
|
47
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
|
|
48
|
+
</button>
|
|
49
|
+
<a href="/" class="logo-link">
|
|
50
|
+
<img alt="MockHTTP" class="logo__img" src="/logo.svg">
|
|
51
|
+
</a>
|
|
52
|
+
<nav class="header-bottom__nav">
|
|
53
|
+
<a class="header-bottom__item" href="/docs/" id="nav-docs">
|
|
54
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>
|
|
55
|
+
<span>Documentation</span>
|
|
56
|
+
</a>
|
|
57
|
+
<a class="header-bottom__item" href="/api" id="nav-api">
|
|
58
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/></svg>
|
|
59
|
+
<span>API Reference</span>
|
|
60
|
+
</a>
|
|
61
|
+
<a class="header-bottom__item" href="/changelog/" id="nav-changelog">
|
|
62
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 6a13 13 0 0 0 8.4-2.8A1 1 0 0 1 21 4v12a1 1 0 0 1-1.6.8A13 13 0 0 0 11 14H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z"/><path d="M6 14a12 12 0 0 0 2.4 7.2 2 2 0 0 0 3.2-2.4A8 8 0 0 1 10 14"/><path d="M8 6v8"/></svg>
|
|
63
|
+
<span>Changelog</span>
|
|
64
|
+
</a>
|
|
65
|
+
</nav>
|
|
66
|
+
<div class="header-actions">
|
|
67
|
+
<button class="search-button" id="search-button" type="button" aria-label="Search" aria-keyshortcuts="Meta+K Control+K">
|
|
68
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
69
|
+
<span class="search-button__text">Search</span>
|
|
70
|
+
<span class="search-button__shortcut">⌘K</span>
|
|
71
|
+
</button>
|
|
72
|
+
<button class="theme-button" id="theme-toggle" aria-label="Theme: system (following system)">
|
|
73
|
+
<svg class="theme-button__icon theme-button__icon--system" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></svg>
|
|
74
|
+
<svg class="theme-button__icon theme-button__icon--sun theme-button__icon--hidden" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
|
|
75
|
+
<svg class="theme-button__icon theme-button__icon--moon theme-button__icon--hidden" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/></svg>
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</header>
|
|
81
|
+
<aside class="mobile-sidebar" id="mobile-sidebar">
|
|
82
|
+
<nav class="mobile-nav">
|
|
83
|
+
<a class="mobile-nav__item" href="/docs/">
|
|
84
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>
|
|
85
|
+
<span>Documentation</span>
|
|
86
|
+
</a>
|
|
87
|
+
<a class="mobile-nav__item" href="/api">
|
|
88
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/></svg>
|
|
89
|
+
<span>API Reference</span>
|
|
90
|
+
</a>
|
|
91
|
+
<a class="mobile-nav__item" href="/changelog/">
|
|
92
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 6a13 13 0 0 0 8.4-2.8A1 1 0 0 1 21 4v12a1 1 0 0 1-1.6.8A13 13 0 0 0 11 14H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z"/><path d="M6 14a12 12 0 0 0 2.4 7.2 2 2 0 0 0 3.2-2.4A8 8 0 0 1 10 14"/><path d="M8 6v8"/></svg>
|
|
93
|
+
<span>Changelog</span>
|
|
94
|
+
</a>
|
|
95
|
+
</nav>
|
|
96
|
+
<div class="mobile-sidebar__docs" id="mobile-sidebar-docs"></div>
|
|
97
|
+
</aside>
|
|
98
|
+
<div class="sidebar-backdrop" id="sidebar-backdrop"></div>
|
|
99
|
+
<div class="search-modal" id="search-modal" hidden>
|
|
100
|
+
<div class="search-modal__backdrop" data-search-close></div>
|
|
101
|
+
<div class="search-modal__panel" role="dialog" aria-modal="true" aria-label="Search">
|
|
102
|
+
<div class="search-modal__header">
|
|
103
|
+
<svg class="search-modal__icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
104
|
+
<input
|
|
105
|
+
type="search"
|
|
106
|
+
id="search-input"
|
|
107
|
+
class="search-modal__input"
|
|
108
|
+
placeholder="Search"
|
|
109
|
+
autocomplete="off"
|
|
110
|
+
autocorrect="off"
|
|
111
|
+
autocapitalize="off"
|
|
112
|
+
spellcheck="false"
|
|
113
|
+
aria-label="Search"
|
|
114
|
+
aria-controls="search-results"
|
|
115
|
+
aria-activedescendant=""
|
|
116
|
+
role="combobox"
|
|
117
|
+
aria-expanded="true"
|
|
118
|
+
/>
|
|
119
|
+
<button class="search-modal__clear" id="search-clear" type="button" aria-label="Clear search" hidden>
|
|
120
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="search-modal__body">
|
|
124
|
+
<ul class="search-results" id="search-results" role="listbox" aria-label="Search results"></ul>
|
|
125
|
+
<div class="search-modal__empty" id="search-empty" hidden>
|
|
126
|
+
No results for "<span class="search-modal__empty-query" id="search-empty-query"></span>"
|
|
127
|
+
</div>
|
|
128
|
+
<div class="search-modal__initial" id="search-initial">
|
|
129
|
+
Type to search the documentation.
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="search-modal__footer">
|
|
133
|
+
<span class="search-modal__hint"><kbd class="search-modal__kbd">↑</kbd><kbd class="search-modal__kbd">↓</kbd> to navigate</span>
|
|
134
|
+
<span class="search-modal__hint"><kbd class="search-modal__kbd">↵</kbd> to select</span>
|
|
135
|
+
<span class="search-modal__hint"><kbd class="search-modal__kbd">esc</kbd> to close</span>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
<script>
|
|
140
|
+
window.__doculaSearch = { indexUrl: "/search-index.json" };
|
|
141
|
+
</script>
|
|
142
|
+
<script src="/js/search.js" defer></script>
|
|
143
|
+
<div class="page-dropdown" id="page-dropdown">
|
|
144
|
+
<button class="page-dropdown__toggle" id="page-dropdown-toggle">
|
|
145
|
+
<span class="page-dropdown__title">Getting Started</span>
|
|
146
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
|
|
147
|
+
</button>
|
|
148
|
+
<div class="page-dropdown__list" id="page-dropdown-list">
|
|
149
|
+
<section class="nav-sidebar__section">
|
|
150
|
+
<ul class="nav-sidebar__list">
|
|
151
|
+
<li>
|
|
152
|
+
<a href="/docs/" class="nav-sidebar__item">Getting Started</a>
|
|
153
|
+
</li>
|
|
154
|
+
</ul>
|
|
155
|
+
</section>
|
|
156
|
+
<section class="nav-sidebar__section">
|
|
157
|
+
<ul class="nav-sidebar__list">
|
|
158
|
+
<li>
|
|
159
|
+
<a href="/docs/https/" class="nav-sidebar__item">HTTPS and HTTP/2</a>
|
|
160
|
+
</li>
|
|
161
|
+
</ul>
|
|
162
|
+
</section>
|
|
163
|
+
<section class="nav-sidebar__section">
|
|
164
|
+
<ul class="nav-sidebar__list">
|
|
165
|
+
<li>
|
|
166
|
+
<a href="/docs/taps/" class="nav-sidebar__item">Taps</a>
|
|
167
|
+
</li>
|
|
168
|
+
</ul>
|
|
169
|
+
</section>
|
|
170
|
+
<section class="nav-sidebar__section">
|
|
171
|
+
<ul class="nav-sidebar__list">
|
|
172
|
+
<li>
|
|
173
|
+
<a href="/docs/bins/" class="nav-sidebar__item">Bins</a>
|
|
174
|
+
</li>
|
|
175
|
+
</ul>
|
|
176
|
+
</section>
|
|
177
|
+
<section class="nav-sidebar__section">
|
|
178
|
+
<ul class="nav-sidebar__list">
|
|
179
|
+
<li>
|
|
180
|
+
<a href="/docs/configuration/" class="nav-sidebar__item">Configuration</a>
|
|
181
|
+
</li>
|
|
182
|
+
</ul>
|
|
183
|
+
</section>
|
|
184
|
+
<section class="nav-sidebar__section">
|
|
185
|
+
<ul class="nav-sidebar__list">
|
|
186
|
+
<li>
|
|
187
|
+
<a href="/docs/library/" class="nav-sidebar__item">Library API</a>
|
|
188
|
+
</li>
|
|
189
|
+
</ul>
|
|
190
|
+
</section>
|
|
191
|
+
<section class="nav-sidebar__section">
|
|
192
|
+
<ul class="nav-sidebar__list">
|
|
193
|
+
<li>
|
|
194
|
+
<a href="/docs/hosted/" class="nav-sidebar__item">Hosted Service</a>
|
|
195
|
+
</li>
|
|
196
|
+
</ul>
|
|
197
|
+
</section>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="content">
|
|
201
|
+
<aside class="nav-sidebar" id="nav-sidebar">
|
|
202
|
+
<section class="nav-sidebar__section">
|
|
203
|
+
<ul class="nav-sidebar__list">
|
|
204
|
+
<li>
|
|
205
|
+
<a href="/docs/" class="nav-sidebar__item">Getting Started</a>
|
|
206
|
+
</li>
|
|
207
|
+
</ul>
|
|
208
|
+
</section>
|
|
209
|
+
<section class="nav-sidebar__section">
|
|
210
|
+
<ul class="nav-sidebar__list">
|
|
211
|
+
<li>
|
|
212
|
+
<a href="/docs/https/" class="nav-sidebar__item">HTTPS and HTTP/2</a>
|
|
213
|
+
</li>
|
|
214
|
+
</ul>
|
|
215
|
+
</section>
|
|
216
|
+
<section class="nav-sidebar__section">
|
|
217
|
+
<ul class="nav-sidebar__list">
|
|
218
|
+
<li>
|
|
219
|
+
<a href="/docs/taps/" class="nav-sidebar__item">Taps</a>
|
|
220
|
+
</li>
|
|
221
|
+
</ul>
|
|
222
|
+
</section>
|
|
223
|
+
<section class="nav-sidebar__section">
|
|
224
|
+
<ul class="nav-sidebar__list">
|
|
225
|
+
<li>
|
|
226
|
+
<a href="/docs/bins/" class="nav-sidebar__item">Bins</a>
|
|
227
|
+
</li>
|
|
228
|
+
</ul>
|
|
229
|
+
</section>
|
|
230
|
+
<section class="nav-sidebar__section">
|
|
231
|
+
<ul class="nav-sidebar__list">
|
|
232
|
+
<li>
|
|
233
|
+
<a href="/docs/configuration/" class="nav-sidebar__item">Configuration</a>
|
|
234
|
+
</li>
|
|
235
|
+
</ul>
|
|
236
|
+
</section>
|
|
237
|
+
<section class="nav-sidebar__section">
|
|
238
|
+
<ul class="nav-sidebar__list">
|
|
239
|
+
<li>
|
|
240
|
+
<a href="/docs/library/" class="nav-sidebar__item">Library API</a>
|
|
241
|
+
</li>
|
|
242
|
+
</ul>
|
|
243
|
+
</section>
|
|
244
|
+
<section class="nav-sidebar__section">
|
|
245
|
+
<ul class="nav-sidebar__list">
|
|
246
|
+
<li>
|
|
247
|
+
<a href="/docs/hosted/" class="nav-sidebar__item">Hosted Service</a>
|
|
248
|
+
</li>
|
|
249
|
+
</ul>
|
|
250
|
+
</section>
|
|
251
|
+
</aside>
|
|
252
|
+
<article class="article">
|
|
253
|
+
<header class="article__header">
|
|
254
|
+
<h1 class="article__title">Getting Started</h1>
|
|
255
|
+
</header>
|
|
256
|
+
<div class="article__body">
|
|
257
|
+
<main class="article__main">
|
|
258
|
+
<p><a href="https://github.com/jaredwray/mockhttp/actions/workflows/tests.yaml"><img src="https://github.com/jaredwray/mockhttp/actions/workflows/tests.yaml/badge.svg" alt="tests"></a>
|
|
259
|
+
<a href="https://github.com/jaredwray/mockhttp/blob/master/LICENSE"><img src="https://img.shields.io/github/license/jaredwray/mockhttp" alt="GitHub license"></a>
|
|
260
|
+
<a href="https://codecov.io/gh/jaredwray/mockhttp"><img src="https://codecov.io/gh/jaredwray/mockhttp/graph/badge.svg?token=eqtqoA3olU" alt="codecov"></a>
|
|
261
|
+
<a href="https://npmjs.com/package/@jaredwray/mockhttp"><img src="https://img.shields.io/npm/dm/@jaredwray/mockhttp" alt="npm"></a>
|
|
262
|
+
<a href="https://npmjs.com/package/@jaredwray/mockhttp"><img src="https://img.shields.io/npm/v/@jaredwray/mockhttp" alt="npm"></a>
|
|
263
|
+
<a href="https://hub.docker.com/r/jaredwray/mockhttp"><img src="https://img.shields.io/docker/pulls/jaredwray/mockhttp" alt="Docker Pulls"></a>
|
|
264
|
+
<a href="https://mockhttp.org"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fjaredwray.com%2Fapi%2Fmockhttp-traffic&query=%24.message&label=mockhttp.org" alt="mockhttp.org"></a></p>
|
|
265
|
+
<p>A simple HTTP server for mocking responses in tests. Inspired by <a href="https://httpbin.org/">httpbin</a> and built with Node.js and Fastify. Run it at <a href="https://mockhttp.org">mockhttp.org</a>, with Docker (<code>jaredwray/mockhttp</code>), or in Node.js (<code>npm install @jaredwray/mockhttp</code>).</p>
|
|
266
|
+
<h2 id="table-of-contents">Table of Contents</h2>
|
|
267
|
+
<ul>
|
|
268
|
+
<li><a href="#features">Features</a></li>
|
|
269
|
+
<li><a href="#deploy-via-docker">Deploy via Docker</a></li>
|
|
270
|
+
<li><a href="#deploy-via-docker-compose">Deploy via Docker Compose</a></li>
|
|
271
|
+
<li><a href="#deploy-via-nodejs">Deploy via Node.js</a></li>
|
|
272
|
+
</ul>
|
|
273
|
+
<h2 id="features">Features</h2>
|
|
274
|
+
<ul>
|
|
275
|
+
<li>All the features of <a href="https://httpbin.org/">httpbin</a></li>
|
|
276
|
+
<li><a href="/docs/taps/">Taps</a> — inject custom responses for testing and development</li>
|
|
277
|
+
<li><a href="/docs/bins/">Bins</a> — capture and inspect incoming HTTP requests (great for webhook debugging)</li>
|
|
278
|
+
<li><code>@fastify/helmet</code> built in by default</li>
|
|
279
|
+
<li>Built with Node.js, TypeScript, and Fastify</li>
|
|
280
|
+
<li>Deploy via Docker or Node.js</li>
|
|
281
|
+
<li>Free hosted service at <a href="https://mockhttp.org">mockhttp.org</a>, running on Cloudflare</li>
|
|
282
|
+
<li>Documentation site and interactive <a href="/api/">OpenAPI reference</a></li>
|
|
283
|
+
<li>Auto-detect the next port that is not in use</li>
|
|
284
|
+
<li><a href="/docs/https/">HTTPS and HTTP/2</a>, plus <a href="/docs/configuration/">rate limiting, logging, and URL matching</a></li>
|
|
285
|
+
</ul>
|
|
286
|
+
<h2 id="deploy-via-docker">Deploy via Docker</h2>
|
|
287
|
+
<pre><code class="hljs language-bash">docker run -d -p 3000:3000 jaredwray/mockhttp
|
|
288
|
+
</code></pre>
|
|
289
|
+
<h2 id="deploy-via-docker-compose">Deploy via Docker Compose</h2>
|
|
290
|
+
<pre><code class="hljs language-yaml"><span class="hljs-attr">services:</span>
|
|
291
|
+
<span class="hljs-attr">mockhttp:</span>
|
|
292
|
+
<span class="hljs-attr">image:</span> <span class="hljs-string">jaredwray/mockhttp:latest</span>
|
|
293
|
+
<span class="hljs-attr">ports:</span>
|
|
294
|
+
<span class="hljs-bullet">-</span> <span class="hljs-string">"3000:3000"</span>
|
|
295
|
+
</code></pre>
|
|
296
|
+
<p>If you want to run it on a different port, just change the <code>3000</code> to whatever port you want and add in the environment variable <code>PORT</code> to the environment.</p>
|
|
297
|
+
<pre><code class="hljs language-yaml"><span class="hljs-attr">services:</span>
|
|
298
|
+
<span class="hljs-attr">mockhttp:</span>
|
|
299
|
+
<span class="hljs-attr">image:</span> <span class="hljs-string">jaredwray/mockhttp:latest</span>
|
|
300
|
+
<span class="hljs-attr">ports:</span>
|
|
301
|
+
<span class="hljs-bullet">-</span> <span class="hljs-string">"3001:3001"</span>
|
|
302
|
+
<span class="hljs-attr">environment:</span>
|
|
303
|
+
<span class="hljs-bullet">-</span> <span class="hljs-string">PORT=3001</span>
|
|
304
|
+
</code></pre>
|
|
305
|
+
<p>You can see an example of this in the <a href="https://github.com/jaredwray/mockhttp/blob/main/docker-compose.yaml">docker-compose.yaml</a> file.</p>
|
|
306
|
+
<h2 id="deploy-via-nodejs">Deploy via Node.js</h2>
|
|
307
|
+
<pre><code class="hljs language-bash">npm install @jaredwray/mockhttp --save
|
|
308
|
+
</code></pre>
|
|
309
|
+
<p>then run <code>mockhttp</code> in your code.</p>
|
|
310
|
+
<pre><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">MockHttp</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@jaredwray/mockhttp'</span>;
|
|
311
|
+
<span class="hljs-keyword">const</span> mock = <span class="hljs-keyword">new</span> <span class="hljs-title class_">MockHttp</span>();
|
|
312
|
+
<span class="hljs-keyword">await</span> mock.<span class="hljs-title function_">start</span>(); <span class="hljs-comment">// start the server</span>
|
|
313
|
+
<span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-string">'http://localhost:3000/get'</span>);
|
|
314
|
+
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(response);
|
|
315
|
+
<span class="hljs-keyword">await</span> mock.<span class="hljs-title function_">close</span>(); <span class="hljs-comment">// stop the server</span>
|
|
316
|
+
</code></pre>
|
|
317
|
+
</main>
|
|
318
|
+
<aside class="content-aside">
|
|
319
|
+
<h2 class="content-aside__title">TABLE OF CONTENTS</h2>
|
|
320
|
+
<ul class="content-aside__list" id="toc-list">
|
|
321
|
+
</ul>
|
|
322
|
+
</aside>
|
|
323
|
+
</div>
|
|
324
|
+
<a class="article__edit-link" href="https://github.com/jaredwray/mockhttp/edit/main/site/docs/index.md" target="_blank" rel="noopener noreferrer">
|
|
325
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
|
|
326
|
+
Edit this page
|
|
327
|
+
</a>
|
|
328
|
+
</article>
|
|
329
|
+
</div>
|
|
330
|
+
<footer>
|
|
331
|
+
<a href="/llms.txt" class="footer__link">llms.txt</a>
|
|
332
|
+
<a href="/feed.xml" class="footer__link" aria-label="RSS Feed">
|
|
333
|
+
<svg class="footer__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="6.18" cy="17.82" r="2.18"/><path d="M4 4.44v2.83c7.03 0 12.73 5.7 12.73 12.73h2.83c0-8.59-6.97-15.56-15.56-15.56zm0 5.66v2.83c3.9 0 7.07 3.17 7.07 7.07h2.83c0-5.47-4.43-9.9-9.9-9.9z"/></svg>
|
|
334
|
+
</a>
|
|
335
|
+
|
|
336
|
+
<a href="https://docula.org/" target="_blank" rel="noopener noreferrer" class="footer__link" aria-label="Docula">
|
|
337
|
+
<svg class="footer__bat" xmlns="http://www.w3.org/2000/svg" viewBox="70 110 360 205" fill="currentColor"><path d="M420,173c-16.76-18-72.78-36.65-131.59-30.58A160.15,160.15,0,0,0,269,120.55c-3.78,4.52-11.15,9.45-19,9.45s-15.23-4.93-19-9.45a160.15,160.15,0,0,0-19.4,21.83C152.78,136.31,96.76,154.94,80,173c48.86,5.86,79.5,34.09,79.5,72.06,52.37,0,77,28,90.5,59.94C263.54,273,288.13,245,340.5,245,340.5,207,371.14,178.8,420,173Z"/><circle cx="239.19" cy="145.36" r="3.32" fill="#fff"/><circle cx="260.81" cy="145.36" r="3.32" fill="#fff"/></svg>
|
|
338
|
+
</a>
|
|
339
|
+
<span class="footer__updated">updated: 2026-09-18</span>
|
|
340
|
+
</footer>
|
|
341
|
+
<script src="/css/highlight/highlight.min.js"></script>
|
|
342
|
+
<script>
|
|
343
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
344
|
+
document.querySelectorAll('pre code').forEach(el => { el.textContent = el.textContent.trimEnd(); });
|
|
345
|
+
hljs.highlightAll();
|
|
346
|
+
var kbd = document.querySelector('.search-button__shortcut');
|
|
347
|
+
if (kbd) kbd.textContent = navigator.platform.indexOf('Mac') > -1 ? '⌘K' : 'Ctrl K';
|
|
348
|
+
|
|
349
|
+
// Copy code buttons
|
|
350
|
+
const copyIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
|
351
|
+
const checkIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
|
|
352
|
+
document.querySelectorAll('pre').forEach(function(pre) {
|
|
353
|
+
const btn = document.createElement('button');
|
|
354
|
+
btn.className = 'copy-code-btn';
|
|
355
|
+
btn.innerHTML = copyIcon;
|
|
356
|
+
btn.setAttribute('aria-label', 'Copy code');
|
|
357
|
+
btn.addEventListener('click', function() {
|
|
358
|
+
const code = pre.querySelector('code');
|
|
359
|
+
const text = code ? code.textContent : pre.textContent;
|
|
360
|
+
navigator.clipboard.writeText(text || '').then(function() {
|
|
361
|
+
btn.innerHTML = checkIcon;
|
|
362
|
+
setTimeout(function() { btn.innerHTML = copyIcon; }, 2000);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
pre.appendChild(btn);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
</script>
|
|
369
|
+
<script>
|
|
370
|
+
(function() {
|
|
371
|
+
var themeKey = window.__doculaThemeKey;
|
|
372
|
+
function getMode() {
|
|
373
|
+
return localStorage.getItem(themeKey) || 'light';
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function resolveTheme(mode) {
|
|
377
|
+
if (mode === 'system') {
|
|
378
|
+
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
379
|
+
}
|
|
380
|
+
return mode;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
var cachedEls = null;
|
|
384
|
+
function getThemeEls() {
|
|
385
|
+
if (!cachedEls) {
|
|
386
|
+
cachedEls = {
|
|
387
|
+
systemIcon: document.querySelector('.theme-button__icon--system'),
|
|
388
|
+
sunIcon: document.querySelector('.theme-button__icon--sun'),
|
|
389
|
+
moonIcon: document.querySelector('.theme-button__icon--moon'),
|
|
390
|
+
toggle: document.getElementById('theme-toggle')
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
return cachedEls;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function applyTheme(mode) {
|
|
397
|
+
var resolved = resolveTheme(mode);
|
|
398
|
+
if (resolved === 'light') {
|
|
399
|
+
document.documentElement.setAttribute('data-theme', 'light');
|
|
400
|
+
} else {
|
|
401
|
+
document.documentElement.removeAttribute('data-theme');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
var els = getThemeEls();
|
|
405
|
+
if (els.systemIcon && els.sunIcon && els.moonIcon) {
|
|
406
|
+
els.systemIcon.classList.add('theme-button__icon--hidden');
|
|
407
|
+
els.sunIcon.classList.add('theme-button__icon--hidden');
|
|
408
|
+
els.moonIcon.classList.add('theme-button__icon--hidden');
|
|
409
|
+
if (mode === 'system') {
|
|
410
|
+
els.systemIcon.classList.remove('theme-button__icon--hidden');
|
|
411
|
+
} else if (mode === 'light') {
|
|
412
|
+
els.sunIcon.classList.remove('theme-button__icon--hidden');
|
|
413
|
+
} else {
|
|
414
|
+
els.moonIcon.classList.remove('theme-button__icon--hidden');
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (els.toggle) {
|
|
419
|
+
var labels = { system: 'Theme: system (following system)', light: 'Theme: light', dark: 'Theme: dark' };
|
|
420
|
+
els.toggle.setAttribute('aria-label', labels[mode] || 'Toggle theme');
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function nextMode(current) {
|
|
425
|
+
var cycle = { system: 'light', light: 'dark', dark: 'system' };
|
|
426
|
+
return cycle[current] || 'system';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
var currentMode = getMode();
|
|
430
|
+
|
|
431
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
432
|
+
applyTheme(currentMode);
|
|
433
|
+
|
|
434
|
+
var els = getThemeEls();
|
|
435
|
+
if (els.toggle) {
|
|
436
|
+
els.toggle.addEventListener('click', function() {
|
|
437
|
+
currentMode = nextMode(currentMode);
|
|
438
|
+
localStorage.setItem(themeKey, currentMode);
|
|
439
|
+
applyTheme(currentMode);
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', function() {
|
|
445
|
+
if (getMode() === 'system') {
|
|
446
|
+
applyTheme('system');
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
})();
|
|
450
|
+
</script>
|
|
451
|
+
<script defer>
|
|
452
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
453
|
+
const menuToggle = document.getElementById('mobile-menu-toggle');
|
|
454
|
+
const mobileSidebar = document.getElementById('mobile-sidebar');
|
|
455
|
+
const backdrop = document.getElementById('sidebar-backdrop');
|
|
456
|
+
if (menuToggle && mobileSidebar) {
|
|
457
|
+
menuToggle.addEventListener('click', () => {
|
|
458
|
+
mobileSidebar.classList.toggle('mobile-sidebar--open');
|
|
459
|
+
if (backdrop) backdrop.classList.toggle('sidebar-backdrop--visible');
|
|
460
|
+
});
|
|
461
|
+
if (backdrop) {
|
|
462
|
+
backdrop.addEventListener('click', () => {
|
|
463
|
+
mobileSidebar.classList.remove('mobile-sidebar--open');
|
|
464
|
+
backdrop.classList.remove('sidebar-backdrop--visible');
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Page dropdown toggle (docs pages)
|
|
470
|
+
const dropdownToggle = document.getElementById('page-dropdown-toggle');
|
|
471
|
+
const dropdownList = document.getElementById('page-dropdown-list');
|
|
472
|
+
if (dropdownToggle && dropdownList) {
|
|
473
|
+
dropdownToggle.addEventListener('click', () => {
|
|
474
|
+
dropdownList.classList.toggle('page-dropdown__list--open');
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Active sidebar link highlighting
|
|
479
|
+
const sidebarLinks = document.querySelectorAll('.nav-sidebar__item');
|
|
480
|
+
const currentPath = window.location.pathname;
|
|
481
|
+
sidebarLinks.forEach((link) => {
|
|
482
|
+
if (link.getAttribute('href') === currentPath || link.getAttribute('href') === currentPath.replace(/\/$/, '')) {
|
|
483
|
+
link.classList.add('nav-sidebar__item--active');
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Sidebar section collapse/expand
|
|
488
|
+
const SIDEBAR_STORAGE_KEY = 'docula:sidebar-sections';
|
|
489
|
+
const collapsibleSections = document.querySelectorAll('.nav-sidebar__section--collapsible');
|
|
490
|
+
if (collapsibleSections.length > 0) {
|
|
491
|
+
let storedSectionState = {};
|
|
492
|
+
try {
|
|
493
|
+
storedSectionState = JSON.parse(localStorage.getItem(SIDEBAR_STORAGE_KEY) || '{}');
|
|
494
|
+
} catch (e) { storedSectionState = {}; }
|
|
495
|
+
|
|
496
|
+
const setSectionOpen = (section, toggle, open) => {
|
|
497
|
+
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
498
|
+
section.classList.toggle('nav-sidebar__section--collapsed', !open);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
collapsibleSections.forEach((section, idx) => {
|
|
502
|
+
const toggle = section.querySelector('.nav-sidebar__toggle');
|
|
503
|
+
const list = section.querySelector('.nav-sidebar__list');
|
|
504
|
+
if (!toggle || !list) return;
|
|
505
|
+
const listId = 'nav-sidebar-section-' + idx;
|
|
506
|
+
list.id = listId;
|
|
507
|
+
toggle.setAttribute('aria-controls', listId);
|
|
508
|
+
|
|
509
|
+
const key = 'section-' + idx;
|
|
510
|
+
const hasStored = Object.prototype.hasOwnProperty.call(storedSectionState, key);
|
|
511
|
+
const isOpen = hasStored ? !!storedSectionState[key] : true;
|
|
512
|
+
setSectionOpen(section, toggle, isOpen);
|
|
513
|
+
|
|
514
|
+
toggle.addEventListener('click', () => {
|
|
515
|
+
const next = toggle.getAttribute('aria-expanded') !== 'true';
|
|
516
|
+
setSectionOpen(section, toggle, next);
|
|
517
|
+
storedSectionState[key] = next;
|
|
518
|
+
try {
|
|
519
|
+
localStorage.setItem(SIDEBAR_STORAGE_KEY, JSON.stringify(storedSectionState));
|
|
520
|
+
} catch (e) { /* storage unavailable */ }
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Active nav link highlighting (header + mobile). Highlight only the
|
|
526
|
+
// single most-specific link: the one whose path is the longest prefix of
|
|
527
|
+
// the current path. A plain startsWith() check lights up every link whose
|
|
528
|
+
// path merely prefixes the current one, so when the site is embedded under
|
|
529
|
+
// a base path (e.g. served at /developer with docs at that root) the
|
|
530
|
+
// Documentation link — whose path is a prefix of every page — would stay
|
|
531
|
+
// highlighted everywhere. Trailing slashes are normalized so matches stop
|
|
532
|
+
// at a path boundary. We read the anchor's resolved origin/pathname so
|
|
533
|
+
// absolute, relative, and same-origin hrefs all work, while cross-origin
|
|
534
|
+
// links and non-anchor items (e.g. the mobile logout button) are skipped.
|
|
535
|
+
const withTrailingSlash = (p) => (p.endsWith('/') ? p : p + '/');
|
|
536
|
+
const here = withTrailingSlash(currentPath);
|
|
537
|
+
const markActiveNav = (selector, activeClass) => {
|
|
538
|
+
let bestLink = null;
|
|
539
|
+
let bestLength = -1;
|
|
540
|
+
document.querySelectorAll(selector).forEach((link) => {
|
|
541
|
+
// Only same-origin anchors can match the current pathname.
|
|
542
|
+
if (!link.href || link.origin !== window.location.origin) return;
|
|
543
|
+
const base = withTrailingSlash(link.pathname);
|
|
544
|
+
if (here.startsWith(base) && base.length > bestLength) {
|
|
545
|
+
bestLink = link;
|
|
546
|
+
bestLength = base.length;
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
if (bestLink) bestLink.classList.add(activeClass);
|
|
550
|
+
};
|
|
551
|
+
markActiveNav('.header-bottom__item', 'header-bottom__item--active');
|
|
552
|
+
markActiveNav('.mobile-nav__item', 'mobile-nav__item--active');
|
|
553
|
+
|
|
554
|
+
// TOC extraction: move the inline TOC into the right sidebar and hide the original
|
|
555
|
+
const tocHeading = document.getElementById('table-of-contents');
|
|
556
|
+
if (tocHeading) {
|
|
557
|
+
const tocList = tocHeading.nextElementSibling;
|
|
558
|
+
const tocTarget = document.getElementById('toc-list');
|
|
559
|
+
if (tocList && tocList.tagName === 'UL' && tocTarget) {
|
|
560
|
+
tocTarget.innerHTML = tocList.innerHTML;
|
|
561
|
+
tocHeading.style.display = 'none';
|
|
562
|
+
tocList.style.display = 'none';
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Hide the TOC aside if it has no entries
|
|
567
|
+
const tocSidebar = document.getElementById('toc-list');
|
|
568
|
+
if (tocSidebar && tocSidebar.children.length === 0) {
|
|
569
|
+
const aside = tocSidebar.closest('.content-aside');
|
|
570
|
+
if (aside) { aside.style.display = 'none'; }
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Image lightbox
|
|
574
|
+
const lightboxOverlay = document.createElement('div');
|
|
575
|
+
lightboxOverlay.className = 'lightbox-overlay';
|
|
576
|
+
lightboxOverlay.addEventListener('click', function(e) {
|
|
577
|
+
if (e.target !== lightboxImg) lightboxOverlay.classList.remove('lightbox-overlay--visible');
|
|
578
|
+
});
|
|
579
|
+
const lightboxClose = document.createElement('button');
|
|
580
|
+
lightboxClose.className = 'lightbox-close';
|
|
581
|
+
lightboxClose.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
|
|
582
|
+
lightboxClose.addEventListener('click', function() {
|
|
583
|
+
lightboxOverlay.classList.remove('lightbox-overlay--visible');
|
|
584
|
+
});
|
|
585
|
+
const lightboxImg = document.createElement('img');
|
|
586
|
+
lightboxOverlay.appendChild(lightboxClose);
|
|
587
|
+
lightboxOverlay.appendChild(lightboxImg);
|
|
588
|
+
document.body.appendChild(lightboxOverlay);
|
|
589
|
+
|
|
590
|
+
document.querySelectorAll('.article__main img, .changelog-entry-body img').forEach(function(img) {
|
|
591
|
+
if (img.closest('a')) return;
|
|
592
|
+
img.addEventListener('click', function() {
|
|
593
|
+
lightboxImg.src = img.src;
|
|
594
|
+
lightboxOverlay.classList.add('lightbox-overlay--visible');
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
</script>
|
|
599
|
+
</body>
|
|
600
|
+
|
|
601
|
+
</html>
|