diff --git a/.github/workflows/action_deploy_docs.yml b/.github/workflows/action_deploy_docs.yml
index c47776be1..cd11f21e9 100644
--- a/.github/workflows/action_deploy_docs.yml
+++ b/.github/workflows/action_deploy_docs.yml
@@ -39,7 +39,7 @@ jobs:
uses: actions/upload-pages-artifact@v4
with:
name: generated_docs
- path: './generated_docs'
+ path: './docs/site'
# Deployment job
deploy:
diff --git a/.github/workflows/action_pull_request.yml b/.github/workflows/action_pull_request.yml
index eb7c79eac..7ded9608d 100644
--- a/.github/workflows/action_pull_request.yml
+++ b/.github/workflows/action_pull_request.yml
@@ -46,7 +46,7 @@ jobs:
- fastlane/Appfile
- Gemfile
- Gemfile.lock
- - .github/workflows/**
+ - .github/workflows/action_pull_request.yml
common-kotlin: &common-kotlin
- *common-fastlane
- build-logic/**
diff --git a/.gitignore b/.gitignore
index c9bf9ebc3..b8703f38f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ generated_docs/*
.kotlin
docs/__pycache__/
docs/site
+docs/docs/overrides/homepage-content.html
**/.gradle/**
**/build/**
**/.idea/**
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fac764f5c..482717c60 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -34,6 +34,29 @@ It's a good idea to sanity check your work by using the library through the demo
Open the root of this repo in Android Studio and run the `samples.demo-kmm.AndroidApp` or `samples.demo-android` targets. The KMM iOS app can also be run through XCode as normal.
Note: Currently there's no way to test the Swift package locally without it first being deployed.
+## Internal API (`@InternalMockzillaApi`)
+
+Some types must be `public` because they are shared across library modules (e.g. DTOs used by both `mockzilla` and `mockzilla-management`), but they are **not intended for use by external consumers**.
+
+These are annotated with `@InternalMockzillaApi` (defined in `mockzilla-common`). The annotation is a `@RequiresOptIn` at the `ERROR` level, so consumers who accidentally reference an internal type will get a compile-time error.
+
+**When to apply it:** Any public declaration (class, interface, function, property) that lives in a `*.internal.*` package.
+
+**When NOT to apply it:**
+- Swift/Objective-C interop entry points (e.g. `AsyncUtils.kt`, `NestedClassBridgeGeneration.kt` on iOS) — Swift has no way to satisfy `@OptIn` requirements, so annotating these would break the Swift bridge.
+- `@JsExport` declarations in `jsinterface/JsInterface.kt` — these are intentionally public JS API even though they happen to live in an `internal` package.
+- Declarations that already have the Kotlin `internal` visibility modifier — the compiler already prevents access, so no annotation is needed (`private` and `internal` Kotlin modifiers should be preferred if they're possible).
+
+**How library modules opt in:** All modules in this repo are inside the internal-API boundary. Rather than adding `@file:OptIn` to every file, each module's `build.gradle.kts` opts in at the module level:
+
+```kotlin
+compilerOptions {
+ freeCompilerArgs.add("-opt-in=com.apadmi.mockzilla.lib.InternalMockzillaApi")
+}
+```
+
+**For `expect`/`actual` declarations:** The annotation must be present on both the `expect` declaration and **every** `actual` declaration across all platforms. Missing one platform will cause a compile error.
+
## Spotless
We use Spotless to reformat and organise all of our library code. It runs automatically on compilation so please ensure you've compiled your code before submitting a pull request.
diff --git a/build-logic/src/main/kotlin/com/apadmi/mockzilla/MobileUiConfig.kt b/build-logic/src/main/kotlin/com/apadmi/mockzilla/MobileUiConfig.kt
index 602b225aa..f2df33c24 100644
--- a/build-logic/src/main/kotlin/com/apadmi/mockzilla/MobileUiConfig.kt
+++ b/build-logic/src/main/kotlin/com/apadmi/mockzilla/MobileUiConfig.kt
@@ -3,7 +3,7 @@ package com.apadmi.mockzilla
import org.gradle.api.Project
object MobileUiConfig {
- const val coreVersionForManagementUi = "3.0.0-alpha2"
+ const val coreVersionForManagementUi = "to be updated during release"
}
fun Project.isMobileUiDeployBuild() = properties["is_building_for_deployment"].toString().toBoolean()
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 000000000..19852566d
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,2 @@
+.venv
+homepage/dist-ssr
\ No newline at end of file
diff --git a/docs/docs/browser_stack.md b/docs/docs/browser_stack.md
index f48632ca9..710c78782 100644
--- a/docs/docs/browser_stack.md
+++ b/docs/docs/browser_stack.md
@@ -12,7 +12,6 @@ Browserstack seems to proxy local traffic by default. In your client app you'll
```kotlin
OkHttpClient.Builder()
.proxy(Proxy.NO_PROXY)
- .protocols(listOf(Protocol.HTTP_1_1)).build()
```
### Ktor Example:
diff --git a/docs/docs/documentation.md b/docs/docs/documentation.md
index 5bee95154..c59149082 100644
--- a/docs/docs/documentation.md
+++ b/docs/docs/documentation.md
@@ -1,17 +1,17 @@
-This documentation is primarily built using [MkDocs](https://www.mkdocs.org/)
-with the [Material theme](https://squidfunk.github.io/mkdocs-material/).
+This documentation is built using [Zensical](https://zensical.org/), a modern static site
+generator by the team behind Material for MkDocs.
-**Their documentation is brilliant so please check their docs if this is not sufficient.**
+**Their documentation is great so check it if this is not sufficient.**
## Working on the HomePage
-The homepage is a separate React site which is included in the MkDocs site.
+The homepage is a separate React site which is included in the Zensical site.
-In your IDE of choice open `docs/homepage` and treat it as regular standalone react site.
+In your IDE of choice open `docs/homepage` and treat it as a regular standalone React site.
Install dependencies with `npm install` and run it with `npm run dev`.
-Note: Run `npm run build` to get your updates to the homepage reflected in the mkdocs site locally.
+Note: Run `npm run build:fragment` (or `./serve.sh`) to get your updates to the homepage reflected in the docs site locally.
## Working on the rest of the documentation
@@ -31,7 +31,11 @@ Tested on python `v{{get_python_version()}}`
```bash
# Install all dependencies
+python3 -m venv .venv
+source .venv/bin/activate
+
pip install -r requirements.txt
+cd homepage && npm install
```
Run the following to start the server.
@@ -40,13 +44,13 @@ This supports hot reloading so updating the docs should
automatically reload the docs in your browser.
```bash
-mkdocs serve
+./serve.sh
```
## Macros
-The docs also uses the [mkdocs-macros](https://mkdocs-macros-plugin.readthedocs.io/en/latest/) plugin.
-This lets us call out to python code (and a load of other features) from within markdown.
+The docs use Zensical's built-in macro support, which is compatible with the `mkdocs-macros-plugin`
+API. This lets us call out to Python code from within Markdown.
See the `main.py` file which includes some useful macros.
diff --git a/docs/docs/endpoints.md b/docs/docs/endpoints.md
index 95e1a3d0c..e548f5b30 100644
--- a/docs/docs/endpoints.md
+++ b/docs/docs/endpoints.md
@@ -1,3 +1,7 @@
+---
+description: Learn how to configure Mockzilla endpoints — define custom handlers for your mock HTTP server.
+---
+
# Configuring Endpoints
## Simple Example
diff --git a/docs/docs/index.md b/docs/docs/index.md
index 487ccfec7..0849dd3bc 100644
--- a/docs/docs/index.md
+++ b/docs/docs/index.md
@@ -1,4 +1,5 @@
---
-title: Home
+title: Mockzilla
template: home.html
+description: A compile safe solution for running and configuring a local HTTP server for your mobile apps. Supports Android, iOS, Kotlin Multiplatform and Flutter.
---
diff --git a/docs/docs/overrides/home.html b/docs/docs/overrides/home.html
index d7b0564c4..e656c186e 100644
--- a/docs/docs/overrides/home.html
+++ b/docs/docs/overrides/home.html
@@ -1,18 +1 @@
-{% extends "main.html" %} {% block header %} {{ super() }} {% endblock %} {% block styles %} {{ super() }}
-
-{% endblock %} {% block footer %} {% endblock %} {% block tabs %} {% block content %}
-
-{% endblock %} {% endblock %}
+{% include "homepage-content.html" %}
diff --git a/docs/docs/overrides/main.html b/docs/docs/overrides/main.html
index e6744171a..5cb6467e6 100644
--- a/docs/docs/overrides/main.html
+++ b/docs/docs/overrides/main.html
@@ -1,44 +1,2 @@
-{% extends "base.html" %} {% block scripts %} {{ super() }}
-
-{% endblock %}
diff --git a/docs/docs/quick-start.md b/docs/docs/quick-start.md
index 2d6a3bf87..5d26cef19 100644
--- a/docs/docs/quick-start.md
+++ b/docs/docs/quick-start.md
@@ -1,3 +1,7 @@
+---
+description: Get started with Mockzilla in minutes. Add the dependency, configure your endpoints, and start a local mock HTTP server for your mobile app.
+---
+
# Quick Start
!!! important
Mockzilla does not support HTTPS, all traffic is cleartext HTTP.
diff --git a/docs/homepage/generate-fragment.mjs b/docs/homepage/generate-fragment.mjs
new file mode 100644
index 000000000..849fd2b60
--- /dev/null
+++ b/docs/homepage/generate-fragment.mjs
@@ -0,0 +1,112 @@
+import { build } from 'vite'
+import { readFileSync, writeFileSync, readdirSync } from 'fs'
+import { resolve, dirname } from 'path'
+import { fileURLToPath, pathToFileURL } from 'url'
+
+const __dirname = dirname(fileURLToPath(import.meta.url))
+const outputPath = resolve(__dirname, '../docs/overrides/homepage-content.html')
+
+// Read version from gradle; VITE_VERSION_NAME env var takes precedence (set by CI via Fastlane).
+const gradleText = readFileSync(resolve(__dirname, '../../mockzilla/build.gradle.kts'), 'utf-8')
+const versionMatch = gradleText.match(/"(.*?)" \/\/ x-release-please-version/)
+process.env.VITE_VERSION_NAME ||= versionMatch ? versionMatch[1] : 'Dev'
+
+// Collapses whitespace and strips single-line comments — sufficient for small inline scripts.
+const minify = src => src.replace(/\/\/[^\n]*/g, '').replace(/\s+/g, ' ').trim()
+
+// Reads Material's /.__palette key before first paint to prevent FOUC.
+// Homepage and docs share this single key so their dark-mode states are always in sync.
+const themeInitScript = minify(`
+ try {
+ var p = localStorage.getItem('/.__palette');
+ var dark = p
+ ? JSON.parse(p).index === 1
+ : window.matchMedia('(prefers-color-scheme:dark)').matches;
+ if (dark) document.documentElement.classList.add('dark');
+ } catch (e) {}
+`)
+
+// Wires up the toggle button and writes Material's /.__palette key on click.
+const toggleScript = minify(`
+ (function () {
+ var b = document.getElementById('theme-toggle');
+ if (!b) return;
+ b.addEventListener('click', function () {
+ var dark = document.documentElement.classList.toggle('dark');
+ try {
+ localStorage.setItem('/.__palette', JSON.stringify({ index: dark ? 1 : 0 }));
+ } catch (e) {}
+ });
+ })()
+`)
+
+// Step 1: Browser build — uses vite.config.mjs (SWC + Tailwind) to compile CSS.
+console.log('Building assets...')
+await build({ logLevel: 'warn' })
+
+// Step 2: SSR build — uses @vitejs/plugin-react (standard Babel, not SWC) because the SWC
+// plugin doesn't reliably produce Node.js-runnable output. react-syntax-highlighter is bundled
+// inline (noExternal) to avoid CJS/ESM interop errors when it's externalized.
+console.log('Building SSR bundle...')
+const { default: reactPlugin } = await import('@vitejs/plugin-react')
+await build({
+ configFile: false,
+ plugins: [reactPlugin()],
+ resolve: {
+ extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
+ alias: { '@': resolve(__dirname, './src') },
+ },
+ logLevel: 'warn',
+ ssr: { noExternal: ['react-syntax-highlighter'] },
+ build: {
+ ssr: 'src/ssr-entry.tsx',
+ outDir: 'dist-ssr',
+ rollupOptions: { output: { format: 'esm' } },
+ },
+})
+
+// Step 3: Run the SSR bundle to get pre-rendered HTML.
+// The cache-bust query param (?t=...) forces Node to re-import on repeated runs.
+console.log('Rendering HTML...')
+const ssrEntryPath = resolve(__dirname, 'dist-ssr/ssr-entry.js')
+const { render } = await import(`${pathToFileURL(ssrEntryPath).href}?t=${Date.now()}`)
+const bodyHtml = render()
+
+// Step 4: Inline the compiled CSS.
+const assetsDir = resolve(__dirname, 'build/homepage-assets')
+const css = readdirSync(assetsDir)
+ .filter(f => f.endsWith('.css'))
+ .map(f => readFileSync(resolve(assetsDir, f), 'utf-8'))
+ .join('\n')
+
+// Step 5: Write the complete standalone HTML document.
+const title = 'Mockzilla — Build API mocks with ease'
+const desc = 'A compile-safe solution for running and configuring a local HTTP server for your mobile apps. Supports Android, iOS, Kotlin Multiplatform and Flutter.'
+const ogUrl = 'https://mockzilla.apadmi.dev/'
+
+const fullDocument = `
+
+
+
+
+${title}
+
+
+
+
+
+
+
+
+
+
+
+${bodyHtml}
+
+
+`
+
+writeFileSync(outputPath, fullDocument)
+console.log(`✓ Standalone HTML document written to ${outputPath}`)
diff --git a/docs/homepage/package-lock.json b/docs/homepage/package-lock.json
index c2deb3321..ab3efef41 100644
--- a/docs/homepage/package-lock.json
+++ b/docs/homepage/package-lock.json
@@ -1187,9 +1187,6 @@
"arm"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1204,9 +1201,6 @@
"arm"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1221,9 +1215,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1238,9 +1229,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1255,9 +1243,6 @@
"loong64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1272,9 +1257,6 @@
"ppc64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1289,9 +1271,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1306,9 +1285,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1323,9 +1299,6 @@
"s390x"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1340,9 +1313,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1357,9 +1327,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1534,9 +1501,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -1554,9 +1518,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -1574,9 +1535,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -1594,9 +1552,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -1812,9 +1767,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1832,9 +1784,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1852,9 +1801,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1872,9 +1818,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -3644,9 +3587,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3668,9 +3608,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3692,9 +3629,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3716,9 +3650,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
diff --git a/docs/homepage/package.json b/docs/homepage/package.json
index 16854a8eb..82377eb93 100644
--- a/docs/homepage/package.json
+++ b/docs/homepage/package.json
@@ -5,6 +5,7 @@
"scripts": {
"dev": "export VITE_VERSION_NAME=Debug; vite",
"build": "tsc -b && vite build",
+ "build:fragment": "node generate-fragment.mjs",
"lint": "eslint .",
"preview": "vite preview"
},
diff --git a/docs/homepage/src/App.tsx b/docs/homepage/src/App.tsx
index 0e5e39fc9..8dc3f3508 100644
--- a/docs/homepage/src/App.tsx
+++ b/docs/homepage/src/App.tsx
@@ -1,15 +1,14 @@
import { Features } from "./components/Features";
import { Footer } from "./components/Footer";
+import { Header } from "./components/Header";
import { Hero } from "./components/Hero";
import { PlatformBanner } from "./components/PlatformBanner";
import { PlatformSupport } from "./components/PlatformSupport";
-import { useTheme } from "./hooks/useTheme";
export default function App() {
- useTheme();
-
return (
+
diff --git a/docs/homepage/src/components/Footer.tsx b/docs/homepage/src/components/Footer.tsx
index 31362072a..504a039ab 100644
--- a/docs/homepage/src/components/Footer.tsx
+++ b/docs/homepage/src/components/Footer.tsx
@@ -1,6 +1,8 @@
-import { Button } from "./ui/button";
+import { buttonVariants } from "./ui/button";
+import { cn } from "./ui/utils";
import { GithubIcon } from "./ui/icons";
-import logo from "../assets/logo.svg";
+
+const logo = "/img/icon.svg";
export function Footer() {
return (
@@ -22,7 +24,6 @@ export function Footer() {
diff --git a/docs/homepage/src/components/Header.tsx b/docs/homepage/src/components/Header.tsx
new file mode 100644
index 000000000..1579d5e4d
--- /dev/null
+++ b/docs/homepage/src/components/Header.tsx
@@ -0,0 +1,69 @@
+import { GithubIcon } from "./ui/icons";
+
+const logo = "/img/icon.svg";
+
+export function Header() {
+ return (
+
+ );
+}
diff --git a/docs/homepage/src/components/Hero.tsx b/docs/homepage/src/components/Hero.tsx
index 6ba8470d4..d4db7ff05 100644
--- a/docs/homepage/src/components/Hero.tsx
+++ b/docs/homepage/src/components/Hero.tsx
@@ -1,43 +1,18 @@
-import { MockzillaLogoDark, MockzillaLogoLight } from "./ui/icons";
-import {
- atomOneDark,
- colorBrewer,
-} from "react-syntax-highlighter/dist/cjs/styles/hljs";
-import { useEffect, useState } from "react";
+import { MockzillaLogoLight } from "./ui/icons";
+import { atomOneDark } from "react-syntax-highlighter/dist/cjs/styles/hljs";
import { ArrowRight } from "lucide-react";
import { Badge } from "./ui/badge";
-import { Button } from "./ui/button";
+import { buttonVariants } from "./ui/button";
+import { cn } from "./ui/utils";
import SyntaxHighlighter from "react-syntax-highlighter";
export function Hero(props: any) {
- const [isDark, setIsDark] = useState(false);
-
- useEffect(() => {
- // Check initial theme
- const checkTheme = () => {
- setIsDark(document.documentElement.classList.contains("dark"));
- };
-
- checkTheme();
-
- // Watch for theme changes
- const observer = new MutationObserver(checkTheme);
- observer.observe(document.documentElement, {
- attributes: true,
- attributeFilter: ["class"],
- });
-
- return () => observer.disconnect();
- }, []);
-
return (
@@ -60,9 +35,7 @@ export function Hero(props: any) {
paddingLeft: "0",
paddingRight: "0",
}}
- style={
- isDark ? atomOneDark : colorBrewer
- }>{`// Configure Server
+ style={atomOneDark}>{`// Configure Server
val endpoint = EndpointConfiguration
.Builder("GET - Customer")
.setDefaultHandler {
@@ -75,7 +48,7 @@ val endpoint = EndpointConfiguration
val config = MockzillaConfig.Builder()
.addEndpoint(endpoint)
-
+
// Start Server
startMockzilla(config)`}
@@ -101,25 +74,23 @@ startMockzilla(config)`}
-
- ((window.top || window).location.href =
- "/quick-start")
- }>
+
Get Started
-
-
- (window.location.href = "#platform-banner")
- }
- className="w-fit group backdrop-blur-sm">
+
+
Learn More
-
+
diff --git a/docs/homepage/src/components/ui/button.tsx b/docs/homepage/src/components/ui/button.tsx
index 78c536dfb..2e0668639 100644
--- a/docs/homepage/src/components/ui/button.tsx
+++ b/docs/homepage/src/components/ui/button.tsx
@@ -54,4 +54,4 @@ function Button({
);
}
-export { Button };
+export { Button, buttonVariants };
diff --git a/docs/homepage/src/components/ui/icons.tsx b/docs/homepage/src/components/ui/icons.tsx
index 4a1e66cd7..3588abb3a 100644
--- a/docs/homepage/src/components/ui/icons.tsx
+++ b/docs/homepage/src/components/ui/icons.tsx
@@ -134,5 +134,4 @@ export const FlutterIcon = (props: SVGProps) => (
);
-export const MockzillaLogoDark = `url('data:image/svg+xml,<%3Fxml version="1.0" encoding="UTF-8" standalone="no"%3F> ')`;
export const MockzillaLogoLight = `url('data:image/svg+xml,<%3Fxml version="1.0" encoding="UTF-8" standalone="no"%3F> ')`;
diff --git a/docs/homepage/src/ssr-entry.tsx b/docs/homepage/src/ssr-entry.tsx
new file mode 100644
index 000000000..31595d1a0
--- /dev/null
+++ b/docs/homepage/src/ssr-entry.tsx
@@ -0,0 +1,6 @@
+import { renderToStaticMarkup } from 'react-dom/server'
+import App from './App'
+
+export function render(): string {
+ return renderToStaticMarkup( )
+}
diff --git a/docs/homepage/src/styles/globals.css b/docs/homepage/src/styles/globals.css
index 62d0f41c0..186a69d08 100644
--- a/docs/homepage/src/styles/globals.css
+++ b/docs/homepage/src/styles/globals.css
@@ -19,7 +19,7 @@
--secondary-foreground: #262626;
--muted: #f5f5f5;
--muted-foreground: #737373;
- --accent: #4eced8;
+ --accent: #3ea6af;
--accent-foreground: #262626;
--success: #4ed887;
--success-foreground: #0d0d0d;
@@ -258,3 +258,9 @@ html {
outline-offset: 2px;
}
}
+
+/* Theme toggle icon visibility — controlled by .dark class on */
+#icon-sun { display: none; }
+#icon-moon { display: block; }
+.dark #icon-sun { display: block; }
+.dark #icon-moon { display: none; }
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
deleted file mode 100644
index f64853152..000000000
--- a/docs/mkdocs.yml
+++ /dev/null
@@ -1,73 +0,0 @@
-site_name: Mockzilla
-site_url: https://mockzilla.apadmi.dev/
-repo_url: https://github.com/Apadmi-Engineering/Mockzilla
-copyright: Copyright © 2025 Apadmi Ltd
-theme:
- logo: "img/icon.svg"
- favicon: "img/favicon.ico"
- custom_dir: docs/overrides
- palette:
- # Palette toggle for light mode
- - media: "(prefers-color-scheme: light)"
- scheme: default
- toggle:
- icon: material/brightness-7
- name: Switch to dark mode
- primary: custom
- accent: custom
-
- # Palette toggle for dark mode
- - media: "(prefers-color-scheme: dark)"
- scheme: slate
- toggle:
- icon: material/brightness-4
- name: Switch to light mode
- primary: custom
- accent: custom
- name: material
- features:
- - navigation.tabs
- - navigation.sections
- - navigation.top
-plugins:
- - search
- - macros
-extra_css:
- - stylesheets/extra.css
-nav:
- - index.md
- - Docs:
- - quick-start.md
- - endpoints.md
- - Advanced:
- - additional_config.md
- - browser_stack.md
- - snapshots.md
- - Configuration at Runtime:
- - desktop/overview.md
- - mobile_ui.md
- - presets.md
- - Contributing:
- - contributing.md
- - Desktop App: desktop_contributing.md
- - Documentation: documentation.md
- - Flutter: flutter_contributing.md
- - Api Reference:
- ./dokka/index.html
-markdown_extensions:
- - pymdownx.arithmatex:
- generic: true
- - admonition
- - pymdownx.details
- - pymdownx.superfences
- - pymdownx.tabbed:
- alternate_style: true
- - pymdownx.superfences:
- custom_fences:
- - name: mermaid
- class: mermaid
- format: !!python/name:pymdownx.superfences.fence_code_format
-extra_javascript:
- - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
-hooks:
- - custom_hooks.py
\ No newline at end of file
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 96d706e0e..fe4ebb73d 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,2 +1 @@
-mkdocs-material==9.6.20
-mkdocs-macros-plugin==1.0.5
\ No newline at end of file
+zensical==0.0.43
\ No newline at end of file
diff --git a/docs/serve.sh b/docs/serve.sh
new file mode 100755
index 000000000..60b406976
--- /dev/null
+++ b/docs/serve.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+
+cd "$SCRIPT_DIR/homepage" && npm run build:fragment
+
+cd "$SCRIPT_DIR"
+if [[ -x "$SCRIPT_DIR/.venv/bin/zensical" ]]; then
+ "$SCRIPT_DIR/.venv/bin/zensical" serve
+ else
+ zensical serve
+ fi
\ No newline at end of file
diff --git a/docs/zensical.toml b/docs/zensical.toml
new file mode 100644
index 000000000..bf72cbdaf
--- /dev/null
+++ b/docs/zensical.toml
@@ -0,0 +1,77 @@
+[project]
+site_name = "Mockzilla"
+site_url = "https://mockzilla.apadmi.dev/"
+repo_url = "https://github.com/Apadmi-Engineering/Mockzilla"
+copyright = "Copyright © 2026 Apadmi Ltd"
+site_description = "A compile-safe solution for running and configuring a local HTTP server for your mobile apps. Supports Android, iOS, Kotlin Multiplatform and Flutter."
+extra_css = ["stylesheets/extra.css"]
+extra_javascript = ["https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"]
+
+nav = [
+ "index.md",
+ { Docs = [
+ "quick-start.md",
+ "endpoints.md",
+ { Advanced = [
+ "additional_config.md",
+ "browser_stack.md",
+ "snapshots.md",
+ ]},
+ { "Configuration at Runtime" = [
+ "desktop/overview.md",
+ "mobile_ui.md",
+ "presets.md",
+ ]},
+ ]},
+ { Contributing = [
+ "contributing.md",
+ { "Desktop App" = "desktop_contributing.md" },
+ { Documentation = "documentation.md" },
+ { Flutter = "flutter_contributing.md" },
+ ]},
+ { "Api Reference" = "./dokka/index.html" },
+]
+
+[project.theme]
+logo = "img/icon.svg"
+favicon = "img/favicon.ico"
+custom_dir = "docs/overrides"
+features = [
+ "navigation.tabs",
+ "navigation.sections",
+ "navigation.top",
+]
+
+[[project.theme.palette]]
+media = "(prefers-color-scheme: light)"
+scheme = "default"
+primary = "custom"
+accent = "custom"
+toggle.icon = "material/brightness-7"
+toggle.name = "Switch to dark mode"
+
+[[project.theme.palette]]
+media = "(prefers-color-scheme: dark)"
+scheme = "slate"
+primary = "custom"
+accent = "custom"
+toggle.icon = "material/brightness-4"
+toggle.name = "Switch to light mode"
+
+[project.markdown_extensions.pymdownx.arithmatex]
+generic = true
+
+[project.markdown_extensions.admonition]
+
+[project.markdown_extensions.pymdownx.details]
+
+[project.markdown_extensions.pymdownx.tabbed]
+alternate_style = true
+
+[project.markdown_extensions.pymdownx.superfences]
+custom_fences = [
+ { name = "mermaid", class = "mermaid", format = "pymdownx.superfences.fence_code_format" }
+]
+
+[project.markdown_extensions.zensical.extensions.macros]
+module_name = "main"
diff --git a/fastlane/fastfiles/docs.rb b/fastlane/fastfiles/docs.rb
index be3eb74b6..4c7497a10 100644
--- a/fastlane/fastfiles/docs.rb
+++ b/fastlane/fastfiles/docs.rb
@@ -1,6 +1,4 @@
lane :generate_docs do
- output_dir = "#{lane_context[:repo_root]}/generated_docs"
-
# Build the page to redirect to the desktop app download site
sh("cd #{lane_context[:repo_root]}/docs; python -c 'import main; main.update_download_file()'")
@@ -9,7 +7,7 @@
cd #{lane_context[:repo_root]}/docs/homepage;
npm i;
export VITE_VERSION_NAME=#{get_core_mockzilla_version_name};
- npm run build;
+ npm run build:fragment;
");
# Generate Kotlin documentation
@@ -20,6 +18,6 @@
}
)
- # Build mkdocs
- sh("cd #{lane_context[:repo_root]}/docs; mkdocs build -d #{output_dir}")
+ # Build docs
+ sh("cd #{lane_context[:repo_root]}/docs; zensical build")
end
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c4e671f16..a4b603bb3 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -18,7 +18,7 @@ buildKonfig = "0.20.0" # https://github.com/yshrsmz/BuildKonfig
dokka = "2.2.0" # https://github.com/Kotlin/dokka
# Localization
-lyricist = "1.8.0" # https://github.com/adrielcafe/lyricist
+lyricist = "1.7.0" # https://github.com/adrielcafe/lyricist
# Networking
ktor = "3.4.3" # https://github.com/ktorio/ktor
@@ -93,6 +93,7 @@ semver = { module = "io.github.z4kn4fein:semver", version.ref = "semver" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
+ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
diff --git a/mockzilla-common/build.gradle.kts b/mockzilla-common/build.gradle.kts
index 732afe3ba..c310208b3 100644
--- a/mockzilla-common/build.gradle.kts
+++ b/mockzilla-common/build.gradle.kts
@@ -79,6 +79,7 @@ kotlin {
}
compilerOptions {
freeCompilerArgs.addAll(CompilerConfig.freeCompilerArgs)
+ freeCompilerArgs.add("-opt-in=com.apadmi.mockzilla.lib.InternalMockzillaApi")
}
}
diff --git a/mockzilla-common/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt b/mockzilla-common/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.android.kt
similarity index 91%
rename from mockzilla-common/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt
rename to mockzilla-common/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.android.kt
index e415c8da8..3dc6db273 100644
--- a/mockzilla-common/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt
+++ b/mockzilla-common/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.android.kt
@@ -1,11 +1,15 @@
+@file:JvmName("FileIoKt")
+
package com.apadmi.mockzilla.lib.internal.utils
import android.annotation.TargetApi
import android.os.Build
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import java.io.File
import java.io.IOException
import java.nio.file.Files
+@InternalMockzillaApi
actual class FileIo(private val cacheDir: File) {
private val cacheDirectory
get() = File(
@@ -44,5 +48,6 @@ actual class FileIo(private val cacheDir: File) {
}
}
+@InternalMockzillaApi
@TargetApi(Build.VERSION_CODES.O)
actual fun createFileIoforTesting() = FileIo(Files.createTempDirectory("").toFile())
diff --git a/mockzilla-common/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.kt b/mockzilla-common/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.android.kt
similarity index 66%
rename from mockzilla-common/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.kt
rename to mockzilla-common/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.android.kt
index 745f795c5..14a30ea71 100644
--- a/mockzilla-common/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.kt
+++ b/mockzilla-common/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.android.kt
@@ -1,6 +1,10 @@
+@file:JvmName("ServerUtilsKt")
+
package com.apadmi.mockzilla.lib.internal.utils
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
+@InternalMockzillaApi
actual val Dispatchers.multiPlatformIo: CoroutineDispatcher get() = Dispatchers.IO
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/InternalMockzillaApi.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/InternalMockzillaApi.kt
similarity index 52%
rename from mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/InternalMockzillaApi.kt
rename to mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/InternalMockzillaApi.kt
index 04f417bc0..f0ce02080 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/InternalMockzillaApi.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/InternalMockzillaApi.kt
@@ -1,8 +1,11 @@
-package com.apadmi.mockzilla.lib.internal.utils
+package com.apadmi.mockzilla.lib
/**
- * API marked with this annotation is internal, and it is not intended to be used outside Mockzilla.
- * It could be modified or removed without any notice. Please do not use it.
+ * API marked with this annotation is internal to Mockzilla and is not intended to be used outside
+ * the library. It could be modified or removed without any notice. Please do not use it.
+ *
+ * Library modules opt in at the module level via `freeCompilerArgs` in their `build.gradle.kts`.
+ * See `CONTRIBUTING.md` for guidance on when and how to apply this annotation.
*/
@RequiresOptIn(
level = RequiresOptIn.Level.ERROR,
@@ -16,7 +19,8 @@ package com.apadmi.mockzilla.lib.internal.utils
AnnotationTarget.FIELD,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.PROPERTY_SETTER,
- AnnotationTarget.PROPERTY_SETTER
+ AnnotationTarget.PROPERTY_GETTER
)
@Retention(AnnotationRetention.BINARY)
+@MustBeDocumented
annotation class InternalMockzillaApi
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/config/ZeroConfConfig.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/config/ZeroConfConfig.kt
index 52e425563..7708a73b2 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/config/ZeroConfConfig.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/config/ZeroConfConfig.kt
@@ -1,8 +1,17 @@
package com.apadmi.mockzilla.lib.config
+/**
+ * Constants for Mockzilla's ZeroConf (Bonjour/DNS-SD) service discovery integration. Used by
+ * the server to advertise itself and by the management UI to locate devices on the network.
+ */
object ZeroConfConfig {
+ /**
+ * The ZeroConf service type Mockzilla registers under.
+ */
const val serviceType = "_mockzilla._tcp"
- // Limit defined here: https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.1
+ /**
+ * Maximum byte length for a ZeroConf service name, as defined by RFC 1035 section 2.3.1.
+ */
const val serviceNameByteLimit = 63
}
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/models/ClearCachesRequestDto.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/models/ClearCachesRequestDto.kt
index c435279b9..b464a9d09 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/models/ClearCachesRequestDto.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/models/ClearCachesRequestDto.kt
@@ -1,11 +1,13 @@
package com.apadmi.mockzilla.lib.internal.models
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import com.apadmi.mockzilla.lib.models.EndpointConfiguration
import kotlinx.serialization.Serializable
/**
* @property keys
*/
+@InternalMockzillaApi
@Serializable
data class ClearCachesRequestDto(
val keys: List
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/models/SerializableEndpointConfig.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/models/SerializableEndpointConfig.kt
index f1c3e0c86..71c32574a 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/models/SerializableEndpointConfig.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/models/SerializableEndpointConfig.kt
@@ -2,6 +2,7 @@
package com.apadmi.mockzilla.lib.internal.models
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import com.apadmi.mockzilla.lib.internal.utils.HttpStatusCodeSerializer
import com.apadmi.mockzilla.lib.models.DashboardOverridePreset
import com.apadmi.mockzilla.lib.models.EndpointConfiguration
@@ -91,6 +92,7 @@ data class SerializableEndpointConfig(
* @property errorHeaders
* @property appliedPresetOverride
*/
+@InternalMockzillaApi
@Suppress("TYPE_ALIAS")
@Serializable
data class SerializableEndpointPatchItemDto(
@@ -137,6 +139,7 @@ data class SerializableEndpointPatchItemDto(
/**
* @property entries
*/
+@InternalMockzillaApi
@Serializable
data class MockDataResponseDto(
val entries: List
@@ -145,6 +148,7 @@ data class MockDataResponseDto(
/**
* @property entries
*/
+@InternalMockzillaApi
@Serializable
data class SerializableEndpointConfigPatchRequestDto(
val entries: List
@@ -152,6 +156,7 @@ data class SerializableEndpointConfigPatchRequestDto(
constructor(entry: SerializableEndpointPatchItemDto) : this(listOf(entry))
}
+@InternalMockzillaApi
@Serializable(with = ServiceResultSerializer::class)
sealed class SetOrDont {
@Serializable
@@ -166,6 +171,7 @@ sealed class SetOrDont {
data class Set(val value: T) : SetOrDont()
}
+@InternalMockzillaApi
class ServiceResultSerializer(
serializer: KSerializer
) : KSerializer> {
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt
index 052669541..1725438be 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt
@@ -1,5 +1,8 @@
package com.apadmi.mockzilla.lib.internal.utils
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
+
+@InternalMockzillaApi
expect class FileIo {
suspend fun readFromCache(filename: String): String?
suspend fun saveToCache(filename: String, contents: String)
@@ -7,4 +10,5 @@ expect class FileIo {
suspend fun deleteAllCaches()
}
+@InternalMockzillaApi
expect fun createFileIoforTesting(): FileIo
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/JsonProvider.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/JsonProvider.kt
index 45243211e..7364b3d40 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/JsonProvider.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/JsonProvider.kt
@@ -1,7 +1,9 @@
package com.apadmi.mockzilla.lib.internal.utils
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import kotlinx.serialization.json.Json
+@InternalMockzillaApi
object JsonProvider {
val json = Json {
ignoreUnknownKeys = true
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.kt
index 9579e15cc..7247a0f67 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.kt
@@ -1,6 +1,8 @@
package com.apadmi.mockzilla.lib.internal.utils
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
+@InternalMockzillaApi
expect val Dispatchers.multiPlatformIo: CoroutineDispatcher
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/models/EndpointConfiguration.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/models/EndpointConfiguration.kt
index 133217c90..48fc53dfa 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/models/EndpointConfiguration.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/models/EndpointConfiguration.kt
@@ -10,15 +10,27 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames
/**
- * @property name
- * @property key
- * @property shouldFail
- * @property delay
- * @property endpointMatcher
- * @property versionCode
- * @property defaultHandler
- * @property errorHandler
- * @property dashboardOptionsConfig
+ * Configures a single mock endpoint within a Mockzilla server. Defines how incoming requests are
+ * matched to this endpoint and what response to return, along with optional dashboard presets and
+ * latency simulation.
+ *
+ * Construct via [Builder].
+ *
+ * @property name Human-readable display name shown in the management dashboard.
+ * @property key Unique identifier for this endpoint, used in management API operations.
+ * @property shouldFail Whether this endpoint returns an error response by default. When `true`,
+ * [errorHandler] is called instead of [defaultHandler].
+ * @property delay Artificial response delay in milliseconds. `null` falls back to the global delay
+ * set on [MockzillaConfig.Builder].
+ * @property dashboardOptionsConfig Preset responses available to users in the management dashboard.
+ * @property versionCode Version number for this endpoint's configuration. Incrementing this value
+ * automatically invalidates any cached responses on connected devices.
+ * @property endpointMatcher Predicate that determines whether an incoming request should be routed
+ * to this endpoint. The first matching endpoint wins.
+ * @property defaultHandler Called when a request matches this endpoint and [shouldFail] is `false`.
+ * Returns the mock response to send back to the caller.
+ * @property errorHandler Called when a request matches this endpoint and [shouldFail] is `true`.
+ * Returns the simulated error response to send back to the caller.
*/
data class EndpointConfiguration(
val name: String,
@@ -32,7 +44,10 @@ data class EndpointConfiguration(
val errorHandler: suspend MockzillaHttpRequest.() -> MockzillaHttpResponse,
) {
/**
- * @property raw
+ * Unique serializable identifier for an [EndpointConfiguration]. Used to reference endpoints
+ * in management API operations such as cache clearing or runtime overrides.
+ *
+ * @property raw The raw string value of the key.
*/
@Serializable
@JvmInline
@@ -59,7 +74,7 @@ data class EndpointConfiguration(
/**
* Sets the human readable name of the endpoint (defaults to the value of the `key`)
*
- * @param name
+ * @param name The human-readable display name for this endpoint.
*/
fun setName(name: String) = apply {
config = config.copy(name = name)
@@ -98,17 +113,17 @@ data class EndpointConfiguration(
* [setShouldFail] causes Mockzilla to generate a failure response, then this block
* will *not* be called, instead the block specified by [setErrorHandler] is called.
*
- * @param handler
+ * @param handler Lambda invoked with the incoming request that returns the mock response.
*/
fun setDefaultHandler(handler: suspend MockzillaHttpRequest.() -> MockzillaHttpResponse) = apply {
config = config.copy(defaultHandler = handler)
}
/**
- * The block called when a network request is made to this endpoint but Mockzilladecides to
+ * The block called when a network request is made to this endpoint but Mockzilla decides to
* simulate a server failure.
*
- * @param handler
+ * @param handler Lambda invoked with the incoming request that returns the simulated error response.
*/
fun setErrorHandler(handler: suspend MockzillaHttpRequest.() -> MockzillaHttpResponse) = apply {
config = config.copy(errorHandler = handler)
@@ -117,8 +132,8 @@ data class EndpointConfiguration(
/**
* Configure the presets that are available to users of the dashboard.
*
- * @param action
- * @return
+ * @param action Builder block for configuring dashboard presets.
+ * @return This builder, for chaining.
*/
fun configureDashboardOverrides(
action: DashboardOptionsConfig.Builder.() -> DashboardOptionsConfig.Builder
@@ -142,7 +157,7 @@ data class EndpointConfiguration(
*
* This is just a utility wrapper around the more flexible [setPatternMatcher] endpoint.
*
- * @param regex
+ * @param regex The regular expression to match against the full request URI.
*/
@Suppress("unused")
fun setPattern(regex: String) = apply {
@@ -170,9 +185,13 @@ data class EndpointConfiguration(
}
/**
- * @property statusCode
- * @property headers
- * @property body
+ * An HTTP response returned by a mock endpoint handler. Returned from
+ * [EndpointConfiguration.Builder.setDefaultHandler] and [EndpointConfiguration.Builder.setErrorHandler]
+ * lambdas.
+ *
+ * @property statusCode The HTTP status code of the response. Defaults to `200 OK`.
+ * @property headers HTTP response headers.
+ * @property body The response body as a string.
*/
@Serializable
data class MockzillaHttpResponse(
@@ -185,9 +204,12 @@ data class MockzillaHttpResponse(
}
/**
- * @property statusCode
- * @property headers
- * @property body
+ * A partial HTTP response used by dashboard presets, allowing a subset of response fields to
+ * be overridden. `null` fields are left unchanged from the endpoint's default response.
+ *
+ * @property statusCode The HTTP status code override, or `null` to leave unchanged.
+ * @property headers HTTP response headers override, or `null` to leave unchanged.
+ * @property body The response body override, or `null` to leave unchanged.
*/
@Serializable
data class PartialMockzillaHttpResponse(
@@ -227,6 +249,12 @@ interface MockzillaHttpRequest {
}
/**
+ * Configures the preset responses available to users in the Mockzilla management dashboard for a
+ * specific endpoint. Presets let dashboard users quickly switch between common response scenarios
+ * without modifying code.
+ *
+ * Construct via [Builder] and attach to an endpoint using
+ * [EndpointConfiguration.Builder.configureDashboardOverrides].
* @property errorPresets
* @property successPresets
*/
@@ -242,10 +270,24 @@ data class DashboardOptionsConfig(
@JsonNames("presets")
val successPresets: List
) {
+ /**
+ * The list of preset responses available in the dashboard for this endpoint.
+ */
val presets get() = successPresets
class Builder {
private val presets = mutableListOf()
+
+ /**
+ * Adds a preset response option to the dashboard for this endpoint.
+ *
+ * @param response The full response this preset applies when selected.
+ * @param name Display name for this preset in the dashboard. Defaults to "Preset N".
+ * @param description Optional description shown alongside the preset.
+ * @param type Visual classification for this preset. Defaults to a type inferred from the
+ * response status code when `null`.
+ * @return This builder, for chaining.
+ */
fun addPreset(
response: MockzillaHttpResponse,
name: String? = null,
@@ -253,6 +295,17 @@ data class DashboardOptionsConfig(
type: DashboardOverridePreset.Type? = null
) = addPreset(response.toPartial(), name, description, type)
+ /**
+ * Adds a partial preset response option to the dashboard for this endpoint. Only fields
+ * set on [response] are overridden; `null` fields retain the endpoint's default values.
+ *
+ * @param response The partial response this preset applies when selected.
+ * @param name Display name for this preset in the dashboard. Defaults to "Preset N".
+ * @param description Optional description shown alongside the preset.
+ * @param type Visual classification for this preset. Defaults to a type inferred from the
+ * response status code when `null`.
+ * @return This builder, for chaining.
+ */
fun addPreset(
response: PartialMockzillaHttpResponse,
name: String? = null,
@@ -290,11 +343,17 @@ data class DashboardOptionsConfig(
}
/**
- * @property name
- * @property description
- * @property type Overrides the type of the preset shown in UI, defaults to correspond with status code
- * @property response
- * @property isManagementUiDefinedCustomPreset
+ * A named response configuration that can be applied to an endpoint from the Mockzilla management
+ * dashboard, overriding the endpoint's default or error response for a session.
+ *
+ * @property name Display name shown in the dashboard preset list.
+ * @property description Optional description shown alongside the preset in the dashboard.
+ * @property type Visual classification for the preset in the dashboard. Defaults to a type
+ * inferred from the response status code when `null`.
+ * @property response The partial response this preset applies when selected.
+ * @property isManagementUiDefinedCustomPreset `true` when this preset was created interactively
+ * by a user in the management dashboard, as opposed to being defined in code via
+ * [EndpointConfiguration.Builder.configureDashboardOverrides].
*/
@Serializable
data class DashboardOverridePreset(
@@ -304,6 +363,10 @@ data class DashboardOverridePreset(
val response: PartialMockzillaHttpResponse,
val isManagementUiDefinedCustomPreset: Boolean = false
) {
+ /**
+ * Visual classification for a [DashboardOverridePreset] in the management dashboard. Used to
+ * display presets with appropriate styling.
+ */
@Serializable
enum class Type {
ClientError,
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/models/MetaData.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/models/MetaData.kt
index 65834d718..4cbdef87d 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/models/MetaData.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/models/MetaData.kt
@@ -8,19 +8,20 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNames
/**
- * @property appName
- * @property appPackage
- * @property operatingSystemVersion
- * @property deviceModel
- * @property appVersion
- * @property runTarget
- * @property mockzillaVersion
+ * Device and application metadata collected when Mockzilla starts. Displayed in the management
+ * dashboard to identify the connected device, and used in ZeroConf service records.
*
- * Don't add non optional fields to this type since that will break backward compatibility
- *
- * Short alternative JsonNames used for encoding/decoding when ZeroConf is used to reduce payload size
+ * Don't add non-optional fields to this type since that will break backward compatibility
*
+ * @property appName The name of the application.
+ * @property appPackage The application package name or bundle identifier.
+ * @property operatingSystemVersion The OS version string of the device.
+ * @property deviceModel The device model identifier.
+ * @property appVersion The application version string.
+ * @property runTarget The platform the server is running on, or `null` if unknown.
+ * @property mockzillaVersion The version of the Mockzilla library.
*/
+@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class MetaData @OptIn(ExperimentalSerializationApi::class) constructor(
@JsonNames("appName")
@@ -51,14 +52,27 @@ data class MetaData @OptIn(ExperimentalSerializationApi::class) constructor(
@SerialName("mzVer")
val mockzillaVersion: String
) {
+ /**
+ * `true` if the server is running on an Android device or emulator.
+ */
val isAndroid = runTarget in listOf(RunTarget.AndroidEmulator, RunTarget.AndroidDevice)
+ /**
+ * Serialises this metadata to a [Map] for embedding in ZeroConf TXT records.
+ *
+ * @return A map of field names to string values.
+ */
fun toMap(): Map {
val encoded = json.encodeToString(this)
return json.decodeFromString>(encoded)
}
companion object {
+ /**
+ * Maximum length in characters for each metadata field. Fields collected from the platform
+ * (device model, OS version, etc.) are truncated to this limit to comply with ZeroConf
+ * DNS-SD payload constraints (RFC 1035).
+ */
const val maxFieldLength = 254
private val json = Json {
isLenient = true
@@ -66,6 +80,13 @@ data class MetaData @OptIn(ExperimentalSerializationApi::class) constructor(
explicitNulls = false
}
+ /**
+ * Deserialises a [MetaData] instance from a [Map] of field names to string values, as
+ * produced by [MetaData.toMap]. Intended for reconstructing metadata received via ZeroConf
+ * TXT records.
+ *
+ * @return The deserialised [MetaData].
+ */
fun Map.parseMetaData(): MetaData {
val encoded = json.encodeToString(this)
return json.decodeFromString(encoded)
@@ -73,6 +94,10 @@ data class MetaData @OptIn(ExperimentalSerializationApi::class) constructor(
}
}
+/**
+ * Identifies the platform on which the Mockzilla server is running. Reported in [MetaData] and
+ * visible in the management dashboard.
+ */
enum class RunTarget {
AndroidDevice,
AndroidEmulator,
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/models/MockzillaConfig.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/models/MockzillaConfig.kt
index 9c95bc691..a1b95a27d 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/models/MockzillaConfig.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/models/MockzillaConfig.kt
@@ -7,14 +7,20 @@ import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
/**
- * @property port
- * @property endpoints
- * @property logLevel
- * @property isRelease
- * @property releaseModeConfig
- * @property localhostOnly
- * @property additionalLogWriters
- * @property isNetworkDiscoveryEnabled
+ * Top-level configuration for a Mockzilla server instance. All properties are set via
+ * [MockzillaConfig.Builder].
+ *
+ * @property port The port the server binds to. `0` causes the OS to assign an available port.
+ * @property endpoints The mock endpoints registered on this server.
+ * @property isRelease When `true`, activates release mode: rate limiting, token authentication,
+ * and localhost-only restrictions are applied. See [ReleaseModeConfig] for details.
+ * @property localhostOnly When `true`, the server only accepts connections from `127.0.0.1`,
+ * blocking the management desktop interface and other external tools.
+ * @property logLevel Verbosity of Mockzilla's internal logging.
+ * @property releaseModeConfig Rate limiting and authentication config applied in release mode.
+ * @property isNetworkDiscoveryEnabled When `true`, Mockzilla broadcasts itself via ZeroConf
+ * (Bonjour) so the management desktop can discover it. Always disabled in release mode.
+ * @property additionalLogWriters Extra log sinks in addition to standard output.
*/
data class MockzillaConfig(
val port: Int,
@@ -26,6 +32,9 @@ data class MockzillaConfig(
val isNetworkDiscoveryEnabled: Boolean,
val additionalLogWriters: List
) {
+ /**
+ * Defines the verbosity of Mockzilla's internal logging.
+ */
enum class LogLevel {
Assert,
Debug,
@@ -72,9 +81,9 @@ data class MockzillaConfig(
/**
* Sets the port which the server will bind to. Setting port to `0` will cause the server to
- * choose it's port auto-magically.
+ * choose its port automatically.
*
- * @param port
+ * @param port Port number to bind to. Use `0` for automatic port assignment.
*/
fun setPort(port: Int): Builder = apply {
this.port = port
@@ -129,7 +138,7 @@ data class MockzillaConfig(
/**
* Enable or disable release mode. See [setReleaseModeConfig] for more details
*
- * @param isRelease
+ * @param isRelease `true` to enable release mode, `false` to disable.
*/
fun setIsReleaseModeEnabled(isRelease: Boolean) = apply {
this.isRelease = isRelease
@@ -139,7 +148,7 @@ data class MockzillaConfig(
* Setting this value to `true` means the mockzilla server will only accept calls from localhost.
* Calls from other IPs will be blocked (including blocking the Mockzilla desktop interface)
*
- * @param localhostOnly
+ * @param localhostOnly `true` to restrict connections to localhost only.
*/
fun setLocalhostOnly(localhostOnly: Boolean) = apply {
this.localhostOnly = localhostOnly
@@ -160,15 +169,15 @@ data class MockzillaConfig(
/**
* Register an new endpoint configuration
*
- * @param endpoint
+ * @param endpoint The endpoint builder to register.
*/
fun addEndpoint(endpoint: EndpointConfiguration.Builder) = addEndpoint(endpoint.build())
/**
* Register an new endpoint configuration
*
- * @param endpoint
- * @return
+ * @param endpoint The endpoint configuration to register.
+ * @return This builder, for chaining.
*/
fun addEndpoint(endpoint: EndpointConfiguration) = apply {
endpoints.add(endpoint)
@@ -179,8 +188,8 @@ data class MockzillaConfig(
*
* Mockzilla logs will then log to standard output and to any additional log writers
*
- * @param logWriter
- * @return
+ * @param logWriter The log writer to register.
+ * @return This builder, for chaining.
*/
fun addLogWriter(logWriter: MockzillaLogWriter) = apply {
additionalLogWriters += logWriter
@@ -197,7 +206,7 @@ data class MockzillaConfig(
/**
* Completes the builder pattern, returning an immutable config.
*
- * @return
+ * @return The fully constructed [MockzillaConfig].
*/
fun build() = MockzillaConfig(port, endpoints.map {
it.copy(
@@ -212,13 +221,18 @@ data class MockzillaConfig(
}
/**
- * @property config
- * @property mockBaseUrl
- * @property apiBaseUrl
- * @property port
- * @property authHeaderProvider
- * @property mockzillaVersion
- * @property ip
+ * Runtime details of a started Mockzilla server, returned by `startMockzilla`. Use [mockBaseUrl]
+ * as the base URL in the app under test's HTTP client to route requests through the mock server.
+ *
+ * @property config The configuration the server was started with.
+ * @property ip The IP address the server is listening on.
+ * @property mockBaseUrl Base URL for mock endpoint requests. Configure the app under test's HTTP
+ * client to use this URL.
+ * @property apiBaseUrl Base URL for the Mockzilla control API.
+ * @property port The port the server is bound to.
+ * @property authHeaderProvider Provides authentication headers for making requests to this server
+ * instance.
+ * @property mockzillaVersion The version of the Mockzilla library.
*/
data class MockzillaRuntimeParams(
val config: MockzillaConfig,
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/service/AuthHeaderProvider.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/service/AuthHeaderProvider.kt
index e4fae77f4..e61a0af24 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/service/AuthHeaderProvider.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/service/AuthHeaderProvider.kt
@@ -1,10 +1,23 @@
package com.apadmi.mockzilla.lib.service
+/**
+ * Generates the authentication header required to make requests to a running Mockzilla server.
+ * An instance pre-configured for the running server is available via
+ * [com.apadmi.mockzilla.lib.models.MockzillaRuntimeParams.authHeaderProvider].
+ */
interface AuthHeaderProvider {
+ /**
+ * Generates a fresh authentication header. Each invocation may produce a new token value.
+ *
+ * @return The header key and value to include in requests to the Mockzilla server.
+ */
suspend fun generateHeader(): Header
+
/**
- * @property key
- * @property value
+ * An HTTP header represented as a key-value pair.
+ *
+ * @property key The header field name.
+ * @property value The header field value.
*/
data class Header(val key: String, val value: String)
}
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/service/MockzillaLogWriter.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/service/MockzillaLogWriter.kt
index 1008138fa..a47a356ac 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/service/MockzillaLogWriter.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/service/MockzillaLogWriter.kt
@@ -2,7 +2,19 @@ package com.apadmi.mockzilla.lib.service
import com.apadmi.mockzilla.lib.models.MockzillaConfig
+/**
+ * Extension point for routing Mockzilla's internal log output to a custom sink, such as a crash
+ * reporting service or custom logger. Register via [MockzillaConfig.Builder.addLogWriter].
+ */
interface MockzillaLogWriter {
+ /**
+ * Called by Mockzilla for each log entry.
+ *
+ * @param logLevel The severity of this log entry.
+ * @param message The log message.
+ * @param tag The source tag identifying the component that produced this log entry.
+ * @param throwable An associated exception, if any.
+ */
fun log(
logLevel: MockzillaConfig.LogLevel,
message: String,
diff --git a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/sharedstate/MockzillaSharedProcessState.kt b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/sharedstate/MockzillaSharedProcessState.kt
index 12c039131..afc02be16 100644
--- a/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/sharedstate/MockzillaSharedProcessState.kt
+++ b/mockzilla-common/src/commonMain/kotlin/com/apadmi/mockzilla/lib/sharedstate/MockzillaSharedProcessState.kt
@@ -5,23 +5,42 @@ import com.apadmi.mockzilla.lib.internal.utils.JsonProvider
import kotlinx.serialization.Serializable
/**
- * @property ip
- * @property port
+ * The IP address and port of a running Mockzilla server, persisted so it can be shared between
+ * processes on the same device. Written by the mockzilla server and read by the management UI module.
+ *
+ * @property ip The IP address the server is listening on.
+ * @property port The port the server is bound to.
*/
@Serializable
data class MockzillaSharedProcessState(val ip: String, val port: Int)
-// Used to share state between `mockzilla` and `mockzilla-mobile-ui` when
-// running on the same device
+/**
+ * Reads and writes [MockzillaSharedProcessState] to a file cache, allowing the Mockzilla server
+ * and the management UI to exchange connection details when running on the same device.
+ */
class MockzillaSharedProcessStateHandler(private val fileIo: FileIo) {
private val fileName = "mockzilla-shared-state.json"
private var sharedState: MockzillaSharedProcessState? = null
+
+ /**
+ * Returns the most recently written [MockzillaSharedProcessState], reading from the file cache
+ * if no value has been set in the current process. Returns `null` if no state has been
+ * persisted yet.
+ *
+ * @return The shared process state, or `null` if unavailable.
+ */
suspend fun getSharedProcessState() = sharedState ?: fileIo.readFromCache(fileName)?.let {
runCatching {
JsonProvider.json.decodeFromString(it)
}.getOrNull()
}
+ /**
+ * Writes [state] to both the in-memory cache and the file cache so it is available to other
+ * processes reading via [getSharedProcessState].
+ *
+ * @param state The server connection details to persist.
+ */
suspend fun setSharedProcessState(state: MockzillaSharedProcessState) {
sharedState = state
fileIo.saveToCache(
diff --git a/mockzilla-common/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt b/mockzilla-common/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.ios.kt
similarity index 93%
rename from mockzilla-common/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt
rename to mockzilla-common/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.ios.kt
index 92137e304..4f480eae5 100644
--- a/mockzilla-common/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt
+++ b/mockzilla-common/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.ios.kt
@@ -1,9 +1,11 @@
package com.apadmi.mockzilla.lib.internal.utils
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import platform.Foundation.*
import kotlinx.cinterop.ExperimentalForeignApi
+@InternalMockzillaApi
@OptIn(ExperimentalForeignApi::class)
actual class FileIo {
private val directoryPath by lazy {
@@ -37,4 +39,5 @@ actual class FileIo {
private fun filePath(filename: String) = "$directoryPath/$filename"
}
+@InternalMockzillaApi
actual fun createFileIoforTesting() = FileIo()
diff --git a/mockzilla-common/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.ios.kt b/mockzilla-common/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.ios.kt
index 9c4926da4..5ac322493 100644
--- a/mockzilla-common/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.ios.kt
+++ b/mockzilla-common/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.ios.kt
@@ -1,7 +1,9 @@
package com.apadmi.mockzilla.lib.internal.utils
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
+@InternalMockzillaApi
actual val Dispatchers.multiPlatformIo: CoroutineDispatcher get() = Dispatchers.IO
diff --git a/mockzilla-common/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt b/mockzilla-common/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.js.kt
similarity index 91%
rename from mockzilla-common/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt
rename to mockzilla-common/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.js.kt
index cbfcd83e3..12b7c9b5f 100644
--- a/mockzilla-common/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt
+++ b/mockzilla-common/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.js.kt
@@ -1,10 +1,12 @@
package com.apadmi.mockzilla.lib.internal.utils
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import kotlin.random.Random
import kotlinx.browser.localStorage
var incrementForUniqueness = 0
+@InternalMockzillaApi
actual class FileIo(private val filePrefix: String = "mockzilla_cache_") {
actual suspend fun readFromCache(filename: String): String? = localStorage.getItem(filePrefix + filename)
@@ -24,6 +26,7 @@ actual class FileIo(private val filePrefix: String = "mockzilla_cache_") {
.forEach { localStorage.removeItem(it) }
}
}
+@InternalMockzillaApi
actual fun createFileIoforTesting() = FileIo(
// Ensure each test has a de-facto isolated storage bucket to prevent overlap
// in parallel tests
diff --git a/mockzilla-common/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.js.kt b/mockzilla-common/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.js.kt
index 082b3637d..60ea569a8 100644
--- a/mockzilla-common/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.js.kt
+++ b/mockzilla-common/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.js.kt
@@ -1,6 +1,8 @@
package com.apadmi.mockzilla.lib.internal.utils
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
+@InternalMockzillaApi
actual val Dispatchers.multiPlatformIo: CoroutineDispatcher get() = Dispatchers.Main
diff --git a/mockzilla-common/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt b/mockzilla-common/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.jvm.kt
similarity index 91%
rename from mockzilla-common/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt
rename to mockzilla-common/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.jvm.kt
index 904d33b4e..fe6c9bae3 100644
--- a/mockzilla-common/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.kt
+++ b/mockzilla-common/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/FileIo.jvm.kt
@@ -1,9 +1,13 @@
+@file:JvmName("FileIoKt")
+
package com.apadmi.mockzilla.lib.internal.utils
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import java.io.File
import java.io.IOException
import java.nio.file.Files
+@InternalMockzillaApi
actual class FileIo(private val cacheDir: File) {
private val cacheDirectory
get() = File(
@@ -43,4 +47,5 @@ actual class FileIo(private val cacheDir: File) {
}
}
+@InternalMockzillaApi
actual fun createFileIoforTesting(): FileIo = FileIo(Files.createTempDirectory("").toFile())
diff --git a/mockzilla-common/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.kt b/mockzilla-common/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.jvm.kt
similarity index 66%
rename from mockzilla-common/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.kt
rename to mockzilla-common/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.jvm.kt
index 745f795c5..14a30ea71 100644
--- a/mockzilla-common/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.kt
+++ b/mockzilla-common/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ServerUtils.jvm.kt
@@ -1,6 +1,10 @@
+@file:JvmName("ServerUtilsKt")
+
package com.apadmi.mockzilla.lib.internal.utils
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
+@InternalMockzillaApi
actual val Dispatchers.multiPlatformIo: CoroutineDispatcher get() = Dispatchers.IO
diff --git a/mockzilla-management-ui/mockzilla-desktop/build.gradle.kts b/mockzilla-management-ui/mockzilla-desktop/build.gradle.kts
index 055f47360..a20ac79a0 100644
--- a/mockzilla-management-ui/mockzilla-desktop/build.gradle.kts
+++ b/mockzilla-management-ui/mockzilla-desktop/build.gradle.kts
@@ -119,6 +119,7 @@ kotlin {
}
compilerOptions {
freeCompilerArgs.addAll(CompilerConfig.freeCompilerArgs)
+ freeCompilerArgs.add("-opt-in=com.apadmi.mockzilla.lib.InternalMockzillaApi")
}
}
diff --git a/mockzilla-management-ui/mockzilla-desktop/src/androidMain/kotlin/com/apadmi/mockzilla/desktop/engine/connection/ZeroConfSdkWrapper.kt b/mockzilla-management-ui/mockzilla-desktop/src/androidMain/kotlin/com/apadmi/mockzilla/desktop/engine/connection/ZeroConfSdkWrapper.android.kt
similarity index 100%
rename from mockzilla-management-ui/mockzilla-desktop/src/androidMain/kotlin/com/apadmi/mockzilla/desktop/engine/connection/ZeroConfSdkWrapper.kt
rename to mockzilla-management-ui/mockzilla-desktop/src/androidMain/kotlin/com/apadmi/mockzilla/desktop/engine/connection/ZeroConfSdkWrapper.android.kt
diff --git a/mockzilla-management-ui/mockzilla-desktop/src/commonMain/kotlin/com/apadmi/mockzilla/desktop/engine/connection/AdbConnectorService.kt b/mockzilla-management-ui/mockzilla-desktop/src/commonMain/kotlin/com/apadmi/mockzilla/desktop/engine/connection/AdbConnectorService.kt
index 9bef3616d..62eac7f85 100644
--- a/mockzilla-management-ui/mockzilla-desktop/src/commonMain/kotlin/com/apadmi/mockzilla/desktop/engine/connection/AdbConnectorService.kt
+++ b/mockzilla-management-ui/mockzilla-desktop/src/commonMain/kotlin/com/apadmi/mockzilla/desktop/engine/connection/AdbConnectorService.kt
@@ -14,7 +14,6 @@ import com.malinskiy.adam.request.forwarding.LocalTcpPortSpec
import com.malinskiy.adam.request.forwarding.PortForwardRequest
import com.malinskiy.adam.request.forwarding.RemoteTcpPortSpec
import com.malinskiy.adam.request.shell.v2.ShellCommandRequest
-
import kotlin.coroutines.cancellation.CancellationException
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
diff --git a/mockzilla-management-ui/mockzilla-desktop/src/desktopMain/kotlin/com/apadmi/mockzilla/desktop/engine/connection/ZeroConfSdkWrapper.kt b/mockzilla-management-ui/mockzilla-desktop/src/desktopMain/kotlin/com/apadmi/mockzilla/desktop/engine/connection/ZeroConfSdkWrapper.desktop.kt
similarity index 100%
rename from mockzilla-management-ui/mockzilla-desktop/src/desktopMain/kotlin/com/apadmi/mockzilla/desktop/engine/connection/ZeroConfSdkWrapper.kt
rename to mockzilla-management-ui/mockzilla-desktop/src/desktopMain/kotlin/com/apadmi/mockzilla/desktop/engine/connection/ZeroConfSdkWrapper.desktop.kt
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/build.gradle.kts b/mockzilla-management-ui/mockzilla-management-ui-common/build.gradle.kts
index 55d46151c..8e33d3554 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/build.gradle.kts
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/build.gradle.kts
@@ -105,15 +105,13 @@ kotlin {
implementation(libs.koin.compose)
implementation(libs.androidx.compose.activity)
- implementation(compose.preview)
- implementation(compose.components.uiToolingPreview)
+ implementation(libs.ui.tooling.preview)
}
- val androidUnitTest by getting {
- dependencies {
- implementation(libs.androidx.test.junit)
- implementation(libs.testParamInjector)
- }
+ androidUnitTest.dependencies {
+ implementation(libs.androidx.test.junit)
+ implementation(libs.testParamInjector)
}
+
val desktopMain by getting {
dependencies {
/* Compose */
@@ -141,14 +139,10 @@ kotlin {
}
compilerOptions {
freeCompilerArgs.addAll(CompilerConfig.freeCompilerArgs)
+ freeCompilerArgs.add("-opt-in=com.apadmi.mockzilla.lib.InternalMockzillaApi")
}
}
-dependencies {
- /* Compose Previews */
- debugImplementation(compose.uiTooling)
-}
-
android {
namespace = "$group.mockzilla.mobile.ui.common"
compileSdk = AndroidConfig.targetSdk
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/di/utils/KoinHandler.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/di/utils/KoinHandler.kt
index 74b7f7776..afe218d59 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/di/utils/KoinHandler.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/di/utils/KoinHandler.kt
@@ -8,6 +8,7 @@ import com.apadmi.mockzilla.ui.engine.device.ActiveDeviceMonitor
import com.apadmi.mockzilla.ui.engine.device.ActiveDeviceSelector
import com.apadmi.mockzilla.ui.engine.events.EventBus
import com.apadmi.mockzilla.ui.engine.events.EventBusImpl
+import com.apadmi.mockzilla.ui.utils.Platform
import org.koin.core.module.Module
import org.koin.dsl.binds
@@ -22,16 +23,22 @@ object MockzillaUiKoinContext {
@OptIn(DelicateCoroutinesApi::class)
private val koinApp = koinApplication {
+ val mockzillaManagement = MockzillaManagement.constructInstance(config = MockzillaManagement.Config(
+ // Bypasses proxy when running on mobile devices since the server is on device
+ // going via a proxy can redirect calls to the proxy machine instead of the local device
+ // (Notably this is needed for Mockzilla to run on Browserstack)
+ disableProxy = Platform.current != Platform.Desktop
+ ))
modules(
viewModelModule(),
useCaseModule(),
module {
- single { MockzillaManagement.instance.appIconService }
- single { MockzillaManagement.instance.metaDataService }
- single { MockzillaManagement.instance.logsService }
- single { MockzillaManagement.instance.endpointsService }
- single { MockzillaManagement.instance.updateService }
- single { MockzillaManagement.instance.cacheClearingService }
+ single { mockzillaManagement.appIconService }
+ single { mockzillaManagement.metaDataService }
+ single { mockzillaManagement.logsService }
+ single { mockzillaManagement.endpointsService }
+ single { mockzillaManagement.updateService }
+ single { mockzillaManagement.cacheClearingService }
single { EventBusImpl(GlobalScope) }
single { ActiveDeviceManagerImpl(get(), GlobalScope) } binds arrayOf(
ActiveDeviceMonitor::class,
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/Config.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/Config.kt
index b5fded64c..5642fd25d 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/Config.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/Config.kt
@@ -3,7 +3,7 @@ package com.apadmi.mockzilla.ui.engine
import com.apadmi.mockzilla.ui.utils.Platform
import io.github.z4kn4fein.semver.Version
-object Config {
+internal object Config {
val minSupportedMockzillaVersion get() = when (Platform.current) {
Platform.Desktop -> Version.parse("1.99.99")
Platform.Android,
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/device/ActiveDeviceManager.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/device/ActiveDeviceManager.kt
index a6b341685..533d0492a 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/device/ActiveDeviceManager.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/device/ActiveDeviceManager.kt
@@ -33,7 +33,7 @@ interface ActiveDeviceSelector {
fun removeDevice(device: Device)
}
-class ActiveDeviceManagerImpl(
+internal class ActiveDeviceManagerImpl(
private val metaDataUseCase: MetaDataUseCase,
private val scope: CoroutineScope
) : ActiveDeviceMonitor, ActiveDeviceSelector {
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/device/MetaDataUseCase.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/device/MetaDataUseCase.kt
index 8e37fb5d1..cd98569e6 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/device/MetaDataUseCase.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/device/MetaDataUseCase.kt
@@ -14,7 +14,7 @@ interface MetaDataUseCase {
suspend fun getMetaData(device: Device, isPolling: Boolean = false): Result
}
-class MetaDataUseCaseImpl(
+internal class MetaDataUseCaseImpl(
private val managementMetaDataService: MockzillaManagement.MetaDataService,
private val currentTimeStamp: TimeStampAccessor = { Clock.System.now().toEpochMilliseconds() }
) : MetaDataUseCase {
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/device/MonitorLogsUseCase.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/device/MonitorLogsUseCase.kt
index f0b36f7ea..23cb478db 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/device/MonitorLogsUseCase.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/device/MonitorLogsUseCase.kt
@@ -5,12 +5,12 @@ import com.apadmi.mockzilla.management.MockzillaManagement
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
-interface MonitorLogsUseCase {
+internal interface MonitorLogsUseCase {
suspend fun getMonitorLogs(device: Device): Result>
suspend fun clearMonitorLogs(device: Device): Result
}
-class MonitorLogsUseCaseImpl(
+internal class MonitorLogsUseCaseImpl(
private val managementLogsService: MockzillaManagement.LogsService,
private val managementMetaDataService: MockzillaManagement.MetaDataService,
) : MonitorLogsUseCase {
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/events/EventBus.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/events/EventBus.kt
index 8244daf6d..ba8f0bf74 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/events/EventBus.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/events/EventBus.kt
@@ -23,7 +23,7 @@ interface EventBus {
}
}
-class EventBusImpl(
+internal class EventBusImpl(
private val coroutineScope: CoroutineScope
) : EventBus {
override val events = MutableSharedFlow()
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/filter/FuzzyFilter.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/filter/FuzzyFilter.kt
index ec8700636..2c7e3d2a7 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/filter/FuzzyFilter.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/filter/FuzzyFilter.kt
@@ -3,7 +3,7 @@ package com.apadmi.mockzilla.ui.engine.filter
import kotlin.math.max
import kotlin.math.min
-data object FuzzyFilter {
+internal data object FuzzyFilter {
/**
* Filters a list of items to only return items matching
* the filter exactly or with minor edits, sorted by the
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/jsoneditor/JsonEditor.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/jsoneditor/JsonEditor.kt
index f1a6135c6..0487a7795 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/jsoneditor/JsonEditor.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/engine/jsoneditor/JsonEditor.kt
@@ -6,7 +6,7 @@ import kotlinx.serialization.json.Json
// or comments are allowed for JSON responses so we can match the application's validation
private val jsonConfiguration = Json
-class JsonEditor(
+internal class JsonEditor(
private val body: String
) {
fun isValidJson(): Boolean = try {
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/components/ForceFailureBanner.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/components/ForceFailureBanner.kt
index 9daca8cc1..c83c1c969 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/components/ForceFailureBanner.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/components/ForceFailureBanner.kt
@@ -42,7 +42,7 @@ import com.apadmi.mockzilla.ui.ui.common.theme.warning
private const val bannerCornerRadius = 8
-enum class ForceFailureBannerState {
+internal enum class ForceFailureBannerState {
FullFailure,
Normal,
PartialFailure,
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/createeditpreset/CreateEditPresetViewModel.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/createeditpreset/CreateEditPresetViewModel.kt
index 88b6fbe03..ed7b7ef6f 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/createeditpreset/CreateEditPresetViewModel.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/createeditpreset/CreateEditPresetViewModel.kt
@@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
-class CreateEditPresetViewModel(
+internal class CreateEditPresetViewModel(
private val key: EndpointConfiguration.Key,
private val device: Device,
private val variant: State.Editing.Variant,
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/createeditpreset/CreateEditPresetWidget.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/createeditpreset/CreateEditPresetWidget.kt
index 6207bdf3d..27d5917f5 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/createeditpreset/CreateEditPresetWidget.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/createeditpreset/CreateEditPresetWidget.kt
@@ -479,7 +479,7 @@ fun CreateEditPresetWidget(
}
@Composable
-fun CreateEditPresetWidgetContent(
+internal fun CreateEditPresetWidgetContent(
state: State,
endpointName: String? = null,
onCancel: () -> Unit = {},
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/details/EndpointDetailsViewModel.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/details/EndpointDetailsViewModel.kt
index 0c8fb652b..16074aea7 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/details/EndpointDetailsViewModel.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/details/EndpointDetailsViewModel.kt
@@ -21,7 +21,7 @@ import kotlinx.coroutines.launch
private typealias UpdateServerBlock = (config: SerializableEndpointConfig, device: Device) -> Unit
private typealias UpdateStateBlock = EndpointDetailsViewModel.State.Endpoint.() -> EndpointDetailsViewModel.State.Endpoint
-class EndpointDetailsViewModel(
+internal class EndpointDetailsViewModel(
private val key: EndpointConfiguration.Key?,
private val device: Device,
private val endpointsService: MockzillaManagement.EndpointsService,
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/details/EndpointDetailsWidget.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/details/EndpointDetailsWidget.kt
index 67a8b547b..577c0be21 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/details/EndpointDetailsWidget.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/details/EndpointDetailsWidget.kt
@@ -271,7 +271,7 @@ fun EndpointDetailsWidget(
}
@Composable
-fun EndpointDetailsWidgetContent(
+internal fun EndpointDetailsWidgetContent(
state: State,
onDelayChange: (Int?) -> Unit,
onFailChange: (Boolean?) -> Unit,
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/endpoints/EndpointsViewModel.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/endpoints/EndpointsViewModel.kt
index 2ec74430b..749c39ec4 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/endpoints/EndpointsViewModel.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/endpoints/endpoints/EndpointsViewModel.kt
@@ -17,7 +17,12 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
-class EndpointsViewModel(
+/** Controls how much information each row in the endpoint list shows. */
+internal enum class RowDensity {
+ Comfy, Compact
+}
+
+internal class EndpointsViewModel(
private val device: Device,
private val endpointsService: MockzillaManagement.EndpointsService,
private val eventBus: EventBus,
@@ -104,15 +109,10 @@ class EndpointsViewModel(
}
}
-/** Controls how much information each row in the endpoint list shows. */
-enum class RowDensity {
- Comfy, Compact
-}
-
/**
* @property displayName
*/
-enum class EndpointProperties(val displayName: String) {
+internal enum class EndpointProperties(val displayName: String) {
Body("Body"),
Delay("Latency"),
Headers("Headers"),
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/metadata/MetaDataWidget.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/metadata/MetaDataWidget.kt
index 126b92ff2..132ab1971 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/metadata/MetaDataWidget.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/metadata/MetaDataWidget.kt
@@ -70,8 +70,31 @@ fun MetaDataWidget(device: Device) {
MetaDataWidgetContent(state, device)
}
+// ── Preview ───────────────────────────────────────────────────────────────────
+
+@Suppress("COMPLEX_EXPRESSION")
+@Preview
@Composable
-fun MetaDataWidgetContent(
+fun MetaDataListViewPreview() = PreviewSurface() {
+ MetaDataListView(
+ state = MetaDataWidgetViewModel.State.DisplayMetaData(
+ metaData = MetaData(
+ appName = "Runner",
+ appPackage = "uk.co.homeserve.pega.sus.internal",
+ operatingSystemVersion = "Version 18.5 (Build 22F77)",
+ deviceModel = "iPhone 16 Plus",
+ appVersion = "999.999.1",
+ mockzillaVersion = "3.0.0-alpha2",
+ runTarget = RunTarget.IosSimulator
+ ),
+ requestCount = 17,
+ ),
+ device = Device(ip = "127.0.0.1", port = "49812")
+ )
+}
+
+@Composable
+internal fun MetaDataWidgetContent(
state: MetaDataWidgetViewModel.State,
device: Device? = null,
strings: Strings = LocalStrings.current
@@ -81,7 +104,12 @@ fun MetaDataWidgetContent(
contentAlignment = Alignment.Center
) {
when (state) {
- is MetaDataWidgetViewModel.State.DisplayMetaData -> MetaDataListView(state, device, strings)
+ is MetaDataWidgetViewModel.State.DisplayMetaData -> MetaDataListView(
+ state,
+ device,
+ strings
+ )
+
MetaDataWidgetViewModel.State.Error -> Text(strings.widgets.metaData.error)
MetaDataWidgetViewModel.State.Loading -> CircularProgressIndicator()
}
@@ -89,11 +117,15 @@ fun MetaDataWidgetContent(
}
@Composable
-fun MetaDataListView(
+internal fun MetaDataListView(
state: MetaDataWidgetViewModel.State.DisplayMetaData,
device: Device? = null,
strings: Strings = LocalStrings.current
-) = Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp)) {
+) = Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 8.dp)
+) {
AppHeader(
appName = state.metaData.appName,
appPackage = state.metaData.appPackage,
@@ -113,7 +145,7 @@ fun MetaDataListView(
}
@Composable
-fun MetaDataRow(
+internal fun MetaDataRow(
label: String,
value: String,
showDivider: Boolean = true
@@ -143,29 +175,6 @@ fun MetaDataRow(
}
}
-// ── Preview ───────────────────────────────────────────────────────────────────
-
-@Suppress("COMPLEX_EXPRESSION")
-@Preview
-@Composable
-fun MetaDataListViewPreview() = PreviewSurface() {
- MetaDataListView(
- state = MetaDataWidgetViewModel.State.DisplayMetaData(
- metaData = MetaData(
- appName = "Runner",
- appPackage = "uk.co.homeserve.pega.sus.internal",
- operatingSystemVersion = "Version 18.5 (Build 22F77)",
- deviceModel = "iPhone 16 Plus",
- appVersion = "999.999.1",
- mockzillaVersion = "3.0.0-alpha2",
- runTarget = RunTarget.IosSimulator
- ),
- requestCount = 17,
- ),
- device = Device(ip = "127.0.0.1", port = "49812")
- )
-}
-
// ── Sections ─────────────────────────────────────────────────────────────────
@Composable
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/metadata/MetaDataWidgetViewModel.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/metadata/MetaDataWidgetViewModel.kt
index 6ebe15cf1..40aa440e8 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/metadata/MetaDataWidgetViewModel.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/metadata/MetaDataWidgetViewModel.kt
@@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
-class MetaDataWidgetViewModel(
+internal class MetaDataWidgetViewModel(
private val device: Device,
private val metaDataUseCase: MetaDataUseCase,
private val monitorLogsUseCase: MonitorLogsUseCase,
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/misccontrols/MiscControlsViewModel.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/misccontrols/MiscControlsViewModel.kt
index 3695309b1..b8f9377f8 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/misccontrols/MiscControlsViewModel.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/misccontrols/MiscControlsViewModel.kt
@@ -8,7 +8,7 @@ import com.apadmi.mockzilla.ui.viewmodel.ViewModel
import kotlinx.coroutines.CoroutineScope
-class MiscControlsViewModel(
+internal class MiscControlsViewModel(
private val device: Device?,
private val eventBus: EventBus,
private val clearingService: MockzillaManagement.CacheClearingService,
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/misccontrols/MiscControlsWidget.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/misccontrols/MiscControlsWidget.kt
index 7008b8fb1..1b47ce7b2 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/misccontrols/MiscControlsWidget.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/misccontrols/MiscControlsWidget.kt
@@ -80,8 +80,17 @@ fun MiscControlsWidget(
)
}
+@Preview
+@Composable
+fun MiscControlsWidgetPreview() = PreviewSurface(darkTheme = true) {
+ MiscControlsWidgetContent(
+ onRefreshAll = {},
+ onClearAllOverrides = {}
+ )
+}
+
@Composable
-fun MiscControlsWidgetContent(
+internal fun MiscControlsWidgetContent(
onRefreshAll: () -> Unit,
onClearAllOverrides: () -> Unit,
strings: Strings = LocalStrings.current
@@ -149,15 +158,6 @@ fun MiscControlsWidgetContent(
}
}
-@Preview
-@Composable
-fun MiscControlsWidgetPreview() = PreviewSurface(darkTheme = true) {
- MiscControlsWidgetContent(
- onRefreshAll = {},
- onClearAllOverrides = {}
- )
-}
-
@Composable
private fun DarkModeSettings(
strings: Strings = LocalStrings.current
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/MonitorLogsViewModel.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/MonitorLogsViewModel.kt
index 2dc76ddc2..2e5fcca20 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/MonitorLogsViewModel.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/MonitorLogsViewModel.kt
@@ -12,7 +12,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
-class MonitorLogsViewModel(
+internal class MonitorLogsViewModel(
private val device: Device,
private val monitorLogsUseCase: MonitorLogsUseCase,
scope: CoroutineScope? = null
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/MonitorLogsWidget.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/MonitorLogsWidget.kt
index a1f9f5ea1..4072864bf 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/MonitorLogsWidget.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/MonitorLogsWidget.kt
@@ -97,95 +97,6 @@ fun MonitorLogsWidget(
)
}
-@Suppress("MAGIC_NUMBER")
-@Composable
-fun MonitorLogsWidgetContent(
- state: MonitorLogsViewModel.State.DisplayLogs,
- onClearAll: () -> Unit,
- onViewDetail: (LogEvent) -> Unit,
- onOpenPanel: () -> Unit = {},
- strings: Strings = LocalStrings.current,
-) {
- val monoFont = LocalMonoFontFamily.current
- val cs = MaterialTheme.colorScheme
- val streamingColor = cs.success.primary
- val dimColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
- val faintColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f)
- val entryList = state.entries.toList()
- val titleStyle = MaterialTheme.typography.labelSmall.copy(
- fontFamily = monoFont,
- fontWeight = FontWeight.SemiBold,
- )
- val logsTitle = strings.widgets.logs.title.uppercase()
-
- var isExpanded by remember { mutableStateOf(false) }
- val chevronRotation by animateFloatAsState(
- targetValue = if (isExpanded) 90f else 0f,
- animationSpec = tween(durationMillis = 150),
- label = "chevronRotation",
- )
-
- Column(modifier = Modifier.background(cs.background)) {
- HorizontalDivider(color = MaterialTheme.colorScheme.outline)
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .clickable { isExpanded = !isExpanded }
- .padding(horizontal = 12.dp, vertical = 7.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(5.dp),
- ) {
- Icon(
- imageVector = Icons.Default.KeyboardArrowRight,
- contentDescription = null,
- tint = faintColor,
- modifier = Modifier.size(14.dp).rotate(chevronRotation),
- )
- Text(
- text = logsTitle,
- style = titleStyle,
- color = dimColor,
- )
- Canvas(modifier = Modifier.size(6.dp)) { drawCircle(color = streamingColor) }
- Text(
- text = strings.widgets.logs.streaming,
- style = MaterialTheme.typography.labelSmall.copy(fontFamily = monoFont),
- color = streamingColor,
- )
- Text(
- text = strings.widgets.logs.clickToInspect,
- style = MaterialTheme.typography.labelSmall.copy(fontFamily = monoFont),
- color = faintColor,
- )
- Spacer(modifier = Modifier.weight(1f))
- Text(
- modifier = Modifier.clickable(onClick = onOpenPanel),
- text = strings.widgets.logs.openInPanel,
- style = MaterialTheme.typography.labelSmall.copy(fontFamily = monoFont),
- color = dimColor,
- )
- }
-
- AnimatedVisibility(
- visible = isExpanded,
- enter = expandVertically(
- expandFrom = Alignment.Top,
- animationSpec = tween(durationMillis = 160),
- ) + fadeIn(animationSpec = tween(durationMillis = 120)),
- exit = shrinkVertically(
- shrinkTowards = Alignment.Top,
- animationSpec = tween(durationMillis = 130),
- ) + fadeOut(animationSpec = tween(durationMillis = 100)),
- ) {
- MonitorLogsList(
- entryList = entryList,
- onViewDetail = onViewDetail,
- modifier = Modifier.height(280.dp),
- )
- }
- }
-}
-
@Suppress("MAGIC_NUMBER")
@Composable
fun LogRow(
@@ -329,6 +240,95 @@ fun MonitorLogsWidgetPreview() = PreviewSurface {
)
}
+@Suppress("MAGIC_NUMBER")
+@Composable
+internal fun MonitorLogsWidgetContent(
+ state: MonitorLogsViewModel.State.DisplayLogs,
+ onClearAll: () -> Unit,
+ onViewDetail: (LogEvent) -> Unit,
+ onOpenPanel: () -> Unit = {},
+ strings: Strings = LocalStrings.current,
+) {
+ val monoFont = LocalMonoFontFamily.current
+ val cs = MaterialTheme.colorScheme
+ val streamingColor = cs.success.primary
+ val dimColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
+ val faintColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f)
+ val entryList = state.entries.toList()
+ val titleStyle = MaterialTheme.typography.labelSmall.copy(
+ fontFamily = monoFont,
+ fontWeight = FontWeight.SemiBold,
+ )
+ val logsTitle = strings.widgets.logs.title.uppercase()
+
+ var isExpanded by remember { mutableStateOf(false) }
+ val chevronRotation by animateFloatAsState(
+ targetValue = if (isExpanded) 90f else 0f,
+ animationSpec = tween(durationMillis = 150),
+ label = "chevronRotation",
+ )
+
+ Column(modifier = Modifier.background(cs.background)) {
+ HorizontalDivider(color = MaterialTheme.colorScheme.outline)
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { isExpanded = !isExpanded }
+ .padding(horizontal = 12.dp, vertical = 7.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(5.dp),
+ ) {
+ Icon(
+ imageVector = Icons.Default.KeyboardArrowRight,
+ contentDescription = null,
+ tint = faintColor,
+ modifier = Modifier.size(14.dp).rotate(chevronRotation),
+ )
+ Text(
+ text = logsTitle,
+ style = titleStyle,
+ color = dimColor,
+ )
+ Canvas(modifier = Modifier.size(6.dp)) { drawCircle(color = streamingColor) }
+ Text(
+ text = strings.widgets.logs.streaming,
+ style = MaterialTheme.typography.labelSmall.copy(fontFamily = monoFont),
+ color = streamingColor,
+ )
+ Text(
+ text = strings.widgets.logs.clickToInspect,
+ style = MaterialTheme.typography.labelSmall.copy(fontFamily = monoFont),
+ color = faintColor,
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ Text(
+ modifier = Modifier.clickable(onClick = onOpenPanel),
+ text = strings.widgets.logs.openInPanel,
+ style = MaterialTheme.typography.labelSmall.copy(fontFamily = monoFont),
+ color = dimColor,
+ )
+ }
+
+ AnimatedVisibility(
+ visible = isExpanded,
+ enter = expandVertically(
+ expandFrom = Alignment.Top,
+ animationSpec = tween(durationMillis = 160),
+ ) + fadeIn(animationSpec = tween(durationMillis = 120)),
+ exit = shrinkVertically(
+ shrinkTowards = Alignment.Top,
+ animationSpec = tween(durationMillis = 130),
+ ) + fadeOut(animationSpec = tween(durationMillis = 100)),
+ ) {
+ MonitorLogsList(
+ entryList = entryList,
+ onViewDetail = onViewDetail,
+ modifier = Modifier.height(280.dp),
+ )
+ }
+ }
+}
+
@Composable
private fun MonitorLogsList(
entryList: List,
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/details/MonitorLogDetailsViewModel.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/details/MonitorLogDetailsViewModel.kt
index 1fd86945e..774baf5e7 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/details/MonitorLogDetailsViewModel.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/details/MonitorLogDetailsViewModel.kt
@@ -5,7 +5,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
-class MonitorLogDetailsViewModel(
+internal class MonitorLogDetailsViewModel(
scope: CoroutineScope? = null
) : ViewModel(scope) {
val state = MutableStateFlow(State.ViewDetails())
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/details/MonitorLogDetailsWidget.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/details/MonitorLogDetailsWidget.kt
index aa1d374bb..69913ea14 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/details/MonitorLogDetailsWidget.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/commonMain/kotlin/com/apadmi/mockzilla/ui/ui/common/widgets/monitorlogs/details/MonitorLogDetailsWidget.kt
@@ -84,9 +84,48 @@ fun MonitorLogDetailsWidget(
}
@Composable
-fun MonitorLogDetailsContent(
+fun MonitorLogDetailsWidgetEmptyPreview() = PreviewSurface {
+ Box(modifier = Modifier.size(300.dp)) {
+ MonitorLogDetailsEmptyContent()
+ }
+}
+
+@Suppress("COMPLEX_EXPRESSION", "MAGIC_NUMBER")
+@Preview
+@Composable
+fun MonitorLogDetailsWidgetPreview() {
+ val previewBody = """{"repairs":[{"id":"HSR-9455","repairStatus":"Upcoming","faultDescription":"Boiler pilot light"}]}"""
+ val previewStatus = HttpStatusCode.OK
+ val authHeader = "Authorization" to "Bearer token123"
+ val contentTypeHeader = "Content-Type" to "application/json"
+ val previewRequestHeaders = mapOf(authHeader)
+ val previewResponseHeaders = mapOf(contentTypeHeader)
+ val previewState = ViewDetails(selectedTab = Tab.Response)
+ val previewEvent = LogEvent(
+ timestamp = 1_716_474_257_201L,
+ url = "https://api.example.com/repairs",
+ requestBody = "",
+ requestHeaders = previewRequestHeaders,
+ responseHeaders = previewResponseHeaders,
+ responseBody = previewBody,
+ status = previewStatus,
+ delay = 342,
+ method = "GET",
+ isIntendedFailure = false,
+ )
+ PreviewSurface {
+ LogDetailsContent(
+ logDetail = previewEvent,
+ state = previewState,
+ onTabSelected = {},
+ )
+ }
+}
+
+@Composable
+internal fun MonitorLogDetailsContent(
logDetail: LogEvent?,
- state: MonitorLogDetailsViewModel.State.ViewDetails,
+ state: ViewDetails,
onTabSelected: (Tab) -> Unit,
onClose: () -> Unit = {},
) {
@@ -99,7 +138,7 @@ fun MonitorLogDetailsContent(
@Suppress("TOO_LONG_FUNCTION")
@Composable
-fun LogDetailsContent(
+internal fun LogDetailsContent(
logDetail: LogEvent,
state: MonitorLogDetailsViewModel.State.ViewDetails,
onTabSelected: (Tab) -> Unit,
@@ -142,7 +181,9 @@ fun LogDetailsContent(
}
@Composable
-fun MonitorLogDetailsEmptyContent(strings: Strings = LocalStrings.current) {
+internal fun MonitorLogDetailsEmptyContent(
+ strings: Strings = LocalStrings.current,
+) {
EmptyState(
title = strings.widgets.logDetails.emptyTitle,
description = strings.widgets.logDetails.emptyDescription,
@@ -158,46 +199,6 @@ fun MonitorLogDetailsEmptyContent(strings: Strings = LocalStrings.current) {
)
}
-@Preview
-@Composable
-fun MonitorLogDetailsWidgetEmptyPreview() = PreviewSurface {
- Box(modifier = Modifier.size(300.dp)) {
- MonitorLogDetailsEmptyContent()
- }
-}
-
-@Suppress("COMPLEX_EXPRESSION", "MAGIC_NUMBER")
-@Preview
-@Composable
-fun MonitorLogDetailsWidgetPreview() {
- val previewBody = """{"repairs":[{"id":"HSR-9455","repairStatus":"Upcoming","faultDescription":"Boiler pilot light"}]}"""
- val previewStatus = HttpStatusCode.OK
- val authHeader = "Authorization" to "Bearer token123"
- val contentTypeHeader = "Content-Type" to "application/json"
- val previewRequestHeaders = mapOf(authHeader)
- val previewResponseHeaders = mapOf(contentTypeHeader)
- val previewState = ViewDetails(selectedTab = Tab.Response)
- val previewEvent = LogEvent(
- timestamp = 1_716_474_257_201L,
- url = "https://api.example.com/repairs",
- requestBody = "",
- requestHeaders = previewRequestHeaders,
- responseHeaders = previewResponseHeaders,
- responseBody = previewBody,
- status = previewStatus,
- delay = 342,
- method = "GET",
- isIntendedFailure = false,
- )
- PreviewSurface {
- LogDetailsContent(
- logDetail = previewEvent,
- state = previewState,
- onTabSelected = {},
- )
- }
-}
-
// ── Header bar ────────────────────────────────────────────────────────────────
@Suppress("MAGIC_NUMBER")
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/desktopTest/kotlin/com/apadmi/mockzilla/ui/ui/widgets/metadata/MetaDataViewModelTests.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/desktopTest/kotlin/com/apadmi/mockzilla/ui/ui/widgets/metadata/MetaDataViewModelTests.kt
index a974b4535..070c8328e 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/desktopTest/kotlin/com/apadmi/mockzilla/ui/ui/widgets/metadata/MetaDataViewModelTests.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/desktopTest/kotlin/com/apadmi/mockzilla/ui/ui/widgets/metadata/MetaDataViewModelTests.kt
@@ -23,7 +23,7 @@ import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlinx.coroutines.yield
-class MetaDataViewModelTests : CoroutineTest() {
+internal class MetaDataViewModelTests : CoroutineTest() {
@RelaxedMockK
lateinit var metaDataUseCaseMock: MetaDataUseCase
diff --git a/mockzilla-management-ui/mockzilla-management-ui-common/src/desktopTest/kotlin/com/apadmi/mockzilla/ui/ui/widgets/monitorlogs/MonitorLogsViewModelTests.kt b/mockzilla-management-ui/mockzilla-management-ui-common/src/desktopTest/kotlin/com/apadmi/mockzilla/ui/ui/widgets/monitorlogs/MonitorLogsViewModelTests.kt
index 34f9931bc..c4dbb5692 100644
--- a/mockzilla-management-ui/mockzilla-management-ui-common/src/desktopTest/kotlin/com/apadmi/mockzilla/ui/ui/widgets/monitorlogs/MonitorLogsViewModelTests.kt
+++ b/mockzilla-management-ui/mockzilla-management-ui-common/src/desktopTest/kotlin/com/apadmi/mockzilla/ui/ui/widgets/monitorlogs/MonitorLogsViewModelTests.kt
@@ -16,7 +16,7 @@ import org.junit.Test
import kotlin.test.assertEquals
import kotlinx.coroutines.yield
-class MonitorLogsViewModelTests : CoroutineTest() {
+internal class MonitorLogsViewModelTests : CoroutineTest() {
private val dummyActiveDevice = Device.dummy()
private val dummyLogEvent = LogEvent(
timestamp = 1,
diff --git a/mockzilla-management-ui/mockzilla-mobile-ui/build.gradle.kts b/mockzilla-management-ui/mockzilla-mobile-ui/build.gradle.kts
index aec1c6c36..b0e564532 100644
--- a/mockzilla-management-ui/mockzilla-mobile-ui/build.gradle.kts
+++ b/mockzilla-management-ui/mockzilla-mobile-ui/build.gradle.kts
@@ -133,6 +133,7 @@ kotlin {
}
compilerOptions {
freeCompilerArgs.addAll(CompilerConfig.freeCompilerArgs)
+ freeCompilerArgs.add("-opt-in=com.apadmi.mockzilla.lib.InternalMockzillaApi")
}
}
diff --git a/mockzilla-management/build.gradle.kts b/mockzilla-management/build.gradle.kts
index 758e9dd2f..e446f6596 100644
--- a/mockzilla-management/build.gradle.kts
+++ b/mockzilla-management/build.gradle.kts
@@ -84,6 +84,12 @@ kotlin {
/* Logging */
implementation(libs.kermit)
}
+ jvmMain.dependencies {
+ implementation(libs.ktor.client.okhttp)
+ }
+ iosMain.dependencies {
+ implementation(libs.ktor.client.darwin)
+ }
commonTest.dependencies {
implementation(kotlin("test"))
implementation(libs.kotlinx.coroutines.test)
@@ -94,6 +100,7 @@ kotlin {
}
compilerOptions {
freeCompilerArgs.addAll(CompilerConfig.freeCompilerArgs)
+ freeCompilerArgs.add("-opt-in=com.apadmi.mockzilla.lib.InternalMockzillaApi")
}
}
diff --git a/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/MockzillaConnectionConfig.kt b/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/MockzillaConnectionConfig.kt
index ca0ef8b8a..e4607551e 100644
--- a/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/MockzillaConnectionConfig.kt
+++ b/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/MockzillaConnectionConfig.kt
@@ -1,9 +1,16 @@
package com.apadmi.mockzilla.management
/**
- * Defines the info needed to create a connection to a device. (i.e. make a request)
+ * Defines the connection details needed to target a specific device running a Mockzilla server.
*/
interface MockzillaConnectionConfig {
+ /**
+ * The IP address of the device.
+ */
val ip: String
+
+ /**
+ * The port the Mockzilla server is bound to on the device.
+ */
val port: String
}
diff --git a/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/MockzillaManagement.kt b/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/MockzillaManagement.kt
index 276b94add..8f3287bf6 100644
--- a/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/MockzillaManagement.kt
+++ b/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/MockzillaManagement.kt
@@ -10,48 +10,150 @@ import com.apadmi.mockzilla.management.internal.MockzillaManagementRepository
import com.apadmi.mockzilla.management.internal.MockzillaManagementRepositoryImpl
import com.apadmi.mockzilla.management.internal.service.UpdateServiceImpl
+/**
+ * A client for remotely manipulating a running Mockzilla server. Used by external tooling (such as
+ * the Mockzilla management desktop app or automated tests) to inspect and control mock endpoint
+ * behaviour at runtime without modifying application code.
+ *
+ * Create an instance via [constructInstance].
+ */
interface MockzillaManagement {
/**
- * In cases where the wrapper isn't granular enough this gives access to handle manually make
- * the raw requests to the server.
+ * Provides direct access to the underlying HTTP repository for cases where the higher-level
+ * services do not cover a required operation. Prefer using the typed service properties where
+ * possible.
+ *
+ * Note: requires `@OptIn(InternalMockzillaApi::class)` since [MockzillaManagementRepository]
+ * is an internal type.
*/
val underlyingRepository: MockzillaManagementRepository
+
+ /**
+ * Service for modifying endpoint behaviour on a connected device at runtime.
+ */
val updateService: UpdateService
+
+ /**
+ * Service for fetching device and application metadata from a connected device.
+ */
val metaDataService: MetaDataService
+
+ /**
+ * Service for fetching monitor logs from a connected device.
+ */
val logsService: LogsService
+
+ /**
+ * Service for clearing endpoint response caches on a connected device.
+ */
val cacheClearingService: CacheClearingService
+
+ /**
+ * Service for querying endpoint configurations from a connected device.
+ */
val endpointsService: EndpointsService
val appIconService: AppIconService
+ /**
+ * Clears endpoint response caches on a connected device. Clearing a cache causes the next
+ * request to that endpoint to return a fresh response rather than a cached one.
+ */
interface CacheClearingService {
+ /**
+ * Clears the response cache for every endpoint on the device at [connection].
+ *
+ * @param connection The device to target.
+ * @return [Result.success] on success, [Result.failure] if the request could not be completed.
+ */
suspend fun clearAllCaches(connection: MockzillaConnectionConfig): Result
+
+ /**
+ * Clears the response cache for the specified endpoints on the device at [connection].
+ *
+ * @param connection The device to target.
+ * @param keys The keys of the endpoints whose caches should be cleared.
+ * @return [Result.success] on success, [Result.failure] if the request could not be completed.
+ */
suspend fun clearCaches(
connection: MockzillaConnectionConfig,
keys: List
): Result
}
+ /**
+ * Queries endpoint configurations from a connected device.
+ */
interface EndpointsService {
+ /**
+ * Fetches the current configuration for all endpoints registered on the device at [connection].
+ *
+ * @param connection The device to target.
+ * @return [Result.success] wrapping the list of endpoint configs, or [Result.failure] if the
+ * request could not be completed.
+ */
suspend fun fetchAllEndpointConfigs(connection: MockzillaConnectionConfig): Result>
+
+ /**
+ * Fetches the dashboard options configuration for the endpoint identified by [key] on the
+ * device at [connection].
+ *
+ * @param connection The device to target.
+ * @param key The key of the endpoint to query.
+ * @return [Result.success] wrapping the dashboard options config, or [Result.failure] if the
+ * request could not be completed.
+ */
suspend fun fetchDashboardOptionsConfig(
connection: MockzillaConnectionConfig,
key: EndpointConfiguration.Key
): Result
}
+ /**
+ * Modifies endpoint behaviour on a connected device at runtime. Changes take effect immediately
+ * and are applied on top of the endpoint's static configuration — passing `null` for any value
+ * resets it to the endpoint's configured default.
+ */
interface UpdateService {
+ /**
+ * Overrides whether the specified endpoints return error responses on the device at
+ * [connection].
+ *
+ * @param connection The device to target.
+ * @param keys The keys of the endpoints to update.
+ * @param shouldFail `true` to force error responses, `false` to force success responses,
+ * `null` to reset to each endpoint's configured default.
+ * @return [Result.success] on success, [Result.failure] if the request could not be completed.
+ */
suspend fun setShouldFail(
connection: MockzillaConnectionConfig,
keys: Collection,
shouldFail: Boolean?
): Result
+ /**
+ * Overrides the response delay for the specified endpoints on the device at [connection].
+ *
+ * @param connection The device to target.
+ * @param keys The keys of the endpoints to update.
+ * @param delayMs The delay to apply in milliseconds, or `null` to reset to each endpoint's
+ * configured default.
+ * @return [Result.success] on success, [Result.failure] if the request could not be completed.
+ */
suspend fun setDelay(
connection: MockzillaConnectionConfig,
keys: Collection,
delayMs: Int?
): Result
+ /**
+ * Applies a dashboard preset to the endpoint identified by [key] on the device at
+ * [connection], overriding the endpoint's response until the override is cleared.
+ *
+ * @param connection The device to target.
+ * @param key The key of the endpoint to update.
+ * @param dashboardOverridePreset The preset to apply.
+ * @return [Result.success] on success, [Result.failure] if the request could not be completed.
+ */
suspend fun applyPreset(
connection: MockzillaConnectionConfig,
key: EndpointConfiguration.Key,
@@ -59,21 +161,66 @@ interface MockzillaManagement {
): Result
}
+ /**
+ * Fetches device and application metadata from a connected device.
+ */
interface MetaDataService {
+ /**
+ * Fetches the device and application metadata from the device at [connection].
+ *
+ * @param connection The device to target.
+ * @param hideFromLogs When `true`, suppresses console logging for this call. Useful for
+ * frequently-polled calls to avoid cluttering the console output.
+ * @return [Result.success] wrapping the device metadata, or [Result.failure] if the
+ * request could not be completed.
+ */
suspend fun fetchMetaData(
connection: MockzillaConnectionConfig,
hideFromLogs: Boolean
): Result
}
+ /**
+ * Fetches monitor logs from a connected device.
+ */
interface LogsService {
+ /**
+ * Fetches all buffered monitor logs from the device at [connection] and clears the buffer.
+ * Subsequent calls will not return the same log entries.
+ *
+ * @param connection The device to target.
+ * @param hideFromLogs When `true`, suppresses console logging for this call. Useful for
+ * frequently-polled calls to avoid cluttering the console output.
+ * @return [Result.success] wrapping the log response, or [Result.failure] if the
+ * request could not be completed.
+ */
suspend fun fetchMonitorLogsAndClearBuffer(
connection: MockzillaConnectionConfig,
hideFromLogs: Boolean
): Result
}
+ /**
+ * Configuration for a [MockzillaManagement] instance.
+ *
+ * @property disableProxy When `true`, management API calls bypass any system-level HTTP proxy
+ * configured on the machine.
+ */
+ data class Config(
+ val disableProxy: Boolean = false
+ )
+
+ /**
+ * Fetches the app icon from a connected device.
+ */
interface AppIconService {
+ /**
+ * Fetches the app icon from the app at [connection] as a byte array
+ *
+ * @param connection The device to target.
+ * @return [Result.success] wrapping the raw byts of the icon, or [Result.failure] if the
+ * request could not be completed.
+ */
suspend fun fetchAppIcon(connection: MockzillaConnectionConfig): Result
}
@@ -88,9 +235,18 @@ interface MockzillaManagement {
) : MockzillaManagement
companion object {
- val instance: MockzillaManagement by lazy {
- val repo = MockzillaManagementRepositoryImpl.create()
- Instance(repo, UpdateServiceImpl(repo), repo, repo, repo, repo, repo)
+ @Deprecated("This property is deprecated")
+ val instance: MockzillaManagement by lazy { constructInstance() }
+
+ /**
+ * Creates a new [MockzillaManagement] instance.
+ *
+ * @param config Configuration for this instance.
+ * @return A fully initialised [MockzillaManagement] ready to connect to devices.
+ */
+ fun constructInstance(config: Config = Config()): MockzillaManagement {
+ val repo = MockzillaManagementRepositoryImpl.create(config)
+ return Instance(repo, UpdateServiceImpl(repo), repo, repo, repo, repo, repo)
}
}
}
diff --git a/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/internal/MockzillaManagementRepositoryImpl.kt b/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/internal/MockzillaManagementRepositoryImpl.kt
index 0992d43a2..33c8cab66 100644
--- a/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/internal/MockzillaManagementRepositoryImpl.kt
+++ b/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/internal/MockzillaManagementRepositoryImpl.kt
@@ -1,5 +1,6 @@
package com.apadmi.mockzilla.management.internal
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import com.apadmi.mockzilla.lib.internal.models.ClearCachesRequestDto
import com.apadmi.mockzilla.lib.internal.models.MockDataResponseDto
import com.apadmi.mockzilla.lib.internal.models.MonitorLogsResponse
@@ -34,6 +35,7 @@ import io.ktor.http.isSuccess
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
+@InternalMockzillaApi
interface MockzillaManagementRepository {
suspend fun fetchMetaData(connection: MockzillaConnectionConfig, hideFromLogs: Boolean): Result
suspend fun fetchAllEndpointConfigs(connection: MockzillaConnectionConfig): Result>
@@ -161,10 +163,13 @@ MockzillaManagement.AppIconService {
}
companion object {
- internal fun create(logger: KtorLogger) = MockzillaManagementRepositoryImpl(
- KtorRequestRunner(KtorClientProvider.createKtorClient(logger = logger))
+ internal fun create(config: MockzillaManagement.Config, logger: KtorLogger) = MockzillaManagementRepositoryImpl(
+ KtorRequestRunner(KtorClientProvider.createKtorClient(
+ disableProxy = config.disableProxy,
+ logger = logger
+ ))
)
- fun create() = create(KtorLogger.SIMPLE)
+ fun create(config: MockzillaManagement.Config) = create(config, KtorLogger.SIMPLE)
}
}
diff --git a/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.kt b/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.kt
index f2b8bb8b4..7f20696de 100644
--- a/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.kt
+++ b/mockzilla-management/src/commonMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.kt
@@ -3,7 +3,6 @@ package com.apadmi.mockzilla.management.internal.ktor
import com.apadmi.mockzilla.lib.internal.utils.JsonProvider
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
-import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
@@ -15,13 +14,14 @@ import io.ktor.serialization.kotlinx.json.json
internal object CustomHeaders {
const val HideFromLogs = "hide-from-logs"
}
+
internal object KtorClientProvider {
- fun createKtorClient(engine: HttpClientEngine? = null, logger: Logger = Logger.SIMPLE) =
- engine?.let {
- HttpClient(engine) {
- httpClientConfig(logger)
- }
- } ?: HttpClient { httpClientConfig(logger) }
+ fun createKtorClient(
+ disableProxy: Boolean,
+ logger: Logger = Logger.SIMPLE
+ ) = createPlatformKtorClient(disableProxy) {
+ httpClientConfig(logger)
+ }
private fun HttpClientConfig<*>.httpClientConfig(logger: Logger) {
install(ContentNegotiation) {
@@ -39,3 +39,8 @@ internal object KtorClientProvider {
install(Resources)
}
}
+
+internal expect fun createPlatformKtorClient(
+ disableProxy: Boolean,
+ configure: HttpClientConfig<*>.() -> Unit
+): HttpClient
diff --git a/mockzilla-management/src/commonTest/kotlin/com/apadmi/mockzilla/management/internal/service/UpdateServiceIntegrationTests.kt b/mockzilla-management/src/commonTest/kotlin/com/apadmi/mockzilla/management/internal/service/UpdateServiceIntegrationTests.kt
index d441e1b6f..0733b3e54 100644
--- a/mockzilla-management/src/commonTest/kotlin/com/apadmi/mockzilla/management/internal/service/UpdateServiceIntegrationTests.kt
+++ b/mockzilla-management/src/commonTest/kotlin/com/apadmi/mockzilla/management/internal/service/UpdateServiceIntegrationTests.kt
@@ -7,6 +7,7 @@ import com.apadmi.mockzilla.lib.models.EndpointConfiguration
import com.apadmi.mockzilla.lib.models.MockzillaConfig
import com.apadmi.mockzilla.lib.models.PartialMockzillaHttpResponse
import com.apadmi.mockzilla.management.MockzillaConnectionConfig
+import com.apadmi.mockzilla.management.MockzillaManagement
import com.apadmi.mockzilla.management.internal.MockzillaManagementRepositoryImpl
import com.apadmi.mockzilla.testutils.runIntegrationTest
import io.ktor.client.plugins.logging.Logger
@@ -20,7 +21,7 @@ class UpdateServiceIntegrationTests {
private suspend fun getEndpointConfig(
connection: MockzillaConnectionConfig
- ) = MockzillaManagementRepositoryImpl.create(Logger.SIMPLE)
+ ) = MockzillaManagementRepositoryImpl.create(MockzillaManagement.Config(), Logger.SIMPLE)
.fetchAllEndpointConfigs(connection)
.getOrThrow()
.first { it.key == dummyConfig.key }
diff --git a/mockzilla-management/src/commonTest/kotlin/com/apadmi/mockzilla/testutils/IntegrationTestRunner.kt b/mockzilla-management/src/commonTest/kotlin/com/apadmi/mockzilla/testutils/IntegrationTestRunner.kt
index 626dd63c6..f73d9d4a8 100644
--- a/mockzilla-management/src/commonTest/kotlin/com/apadmi/mockzilla/testutils/IntegrationTestRunner.kt
+++ b/mockzilla-management/src/commonTest/kotlin/com/apadmi/mockzilla/testutils/IntegrationTestRunner.kt
@@ -5,6 +5,7 @@ import com.apadmi.mockzilla.lib.models.MockzillaConfig
import com.apadmi.mockzilla.lib.models.MockzillaRuntimeParams
import com.apadmi.mockzilla.lib.stopMockzilla
import com.apadmi.mockzilla.management.MockzillaConnectionConfig
+import com.apadmi.mockzilla.management.MockzillaManagement.*
import com.apadmi.mockzilla.management.internal.MockzillaManagementRepository
import com.apadmi.mockzilla.management.internal.MockzillaManagementRepositoryImpl
import io.ktor.client.plugins.logging.Logger
@@ -35,7 +36,10 @@ internal fun runIntegrationTest(
testBlock: TestBlock
) = runTest {
/* Setup */
- val repo = MockzillaManagementRepositoryImpl.create(logger = Logger.SIMPLE)
+ val repo = MockzillaManagementRepositoryImpl.create(
+ config = Config(),
+ logger = Logger.SIMPLE
+ )
val runtimeParams = startTestingMockzilla(appName, appVersion, config)
/* Run Test */
diff --git a/mockzilla-management/src/iosMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.ios.kt b/mockzilla-management/src/iosMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.ios.kt
new file mode 100644
index 000000000..899a20c65
--- /dev/null
+++ b/mockzilla-management/src/iosMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.ios.kt
@@ -0,0 +1,20 @@
+package com.apadmi.mockzilla.management.internal.ktor
+
+import io.ktor.client.HttpClient
+import io.ktor.client.engine.darwin.Darwin
+
+internal actual fun createPlatformKtorClient(
+ disableProxy: Boolean,
+ configure: io.ktor.client.HttpClientConfig<*>.() -> Unit
+) = HttpClient(Darwin) {
+ engine {
+ if (disableProxy) {
+ configureSession {
+ // Empty dictionary means no proxy
+ connectionProxyDictionary = emptyMap()
+ }
+ }
+ }
+
+ configure()
+}
diff --git a/mockzilla-management/src/jsMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.jsMain.kt b/mockzilla-management/src/jsMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.jsMain.kt
new file mode 100644
index 000000000..27ae71a7f
--- /dev/null
+++ b/mockzilla-management/src/jsMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.jsMain.kt
@@ -0,0 +1,11 @@
+package com.apadmi.mockzilla.management.internal.ktor
+
+import io.ktor.client.HttpClient
+import io.ktor.client.HttpClientConfig
+
+internal actual fun createPlatformKtorClient(
+ disableProxy: Boolean,
+ configure: HttpClientConfig<*>.() -> Unit
+) = HttpClient {
+ configure()
+}
diff --git a/mockzilla-management/src/jvmMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.jvm.kt b/mockzilla-management/src/jvmMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.jvm.kt
new file mode 100644
index 000000000..0e270d979
--- /dev/null
+++ b/mockzilla-management/src/jvmMain/kotlin/com/apadmi/mockzilla/management/internal/ktor/KtorClientProvider.jvm.kt
@@ -0,0 +1,22 @@
+package com.apadmi.mockzilla.management.internal.ktor
+
+import io.ktor.client.HttpClient
+import io.ktor.client.HttpClientConfig
+import io.ktor.client.engine.okhttp.OkHttp
+import okhttp3.OkHttpClient
+import java.net.Proxy
+
+internal actual fun createPlatformKtorClient(
+ disableProxy: Boolean,
+ configure: HttpClientConfig<*>.() -> Unit
+) = HttpClient(OkHttp) {
+ engine {
+ if (disableProxy) {
+ preconfigured = OkHttpClient.Builder()
+ .proxy(Proxy.NO_PROXY)
+ .build()
+ }
+ }
+
+ configure()
+}
diff --git a/mockzilla/build.gradle.kts b/mockzilla/build.gradle.kts
index 05f01973c..e552e1277 100644
--- a/mockzilla/build.gradle.kts
+++ b/mockzilla/build.gradle.kts
@@ -137,6 +137,7 @@ kotlin {
}
compilerOptions {
freeCompilerArgs.addAll(CompilerConfig.freeCompilerArgs)
+ freeCompilerArgs.add("-opt-in=com.apadmi.mockzilla.lib.InternalMockzillaApi")
}
js {
diff --git a/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/AndroidMockzilla.kt b/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.android.kt
similarity index 96%
rename from mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/AndroidMockzilla.kt
rename to mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.android.kt
index 53e482156..d5c76ee3e 100644
--- a/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/AndroidMockzilla.kt
+++ b/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.android.kt
@@ -1,3 +1,5 @@
+@file:JvmName("AndroidMockzillaKt")
+
package com.apadmi.mockzilla.lib
import android.content.Context
@@ -45,9 +47,8 @@ fun startMockzilla(config: MockzillaConfig, context: Context): MockzillaRuntimeP
}
/**
- * Stops the Mockzilla server,
+ * Stops the running Mockzilla server.
*
- * @return
*/
actual fun stopMockzilla() = runBlocking {
stopServer()
diff --git a/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/discovery/ZeroConfDiscoveryServiceImpl.kt b/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/discovery/ZeroConfDiscoveryServiceImpl.kt
index b59e964e3..f268926c3 100644
--- a/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/discovery/ZeroConfDiscoveryServiceImpl.kt
+++ b/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/discovery/ZeroConfDiscoveryServiceImpl.kt
@@ -14,7 +14,7 @@ import com.google.android.gms.common.GoogleApiAvailabilityLight
import java.util.UUID
-class ZeroConfDiscoveryServiceImpl(
+internal class ZeroConfDiscoveryServiceImpl(
private val logger: Logger,
private val context: Context
) : ZeroConfDiscoveryService {
diff --git a/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/AndroidErrorUtils.kt b/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/AndroidErrorUtils.kt
index de31fa6de..12d602124 100644
--- a/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/AndroidErrorUtils.kt
+++ b/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/AndroidErrorUtils.kt
@@ -2,4 +2,4 @@
package com.apadmi.mockzilla.lib.internal.utils
-actual typealias AddressAlreadyInUseException = java.net.BindException
+internal actual typealias AddressAlreadyInUseException = java.net.BindException
diff --git a/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/EmulatorUtils.kt b/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/EmulatorUtils.kt
index 7f3f6424f..c747b2a0c 100644
--- a/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/EmulatorUtils.kt
+++ b/mockzilla/src/androidMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/EmulatorUtils.kt
@@ -10,7 +10,7 @@ import android.annotation.SuppressLint
import java.io.*
import java.lang.reflect.Method
-val isProbablyRunningOnEmulator: Boolean by lazy {
+internal val isProbablyRunningOnEmulator: Boolean by lazy {
// Android SDK emulator
return@lazy ((Build.MANUFACTURER == "Google" && Build.BRAND == "google" &&
((Build.FINGERPRINT.startsWith("google/sdk_gphone_")
diff --git a/mockzilla/src/androidUnitTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.kt b/mockzilla/src/androidUnitTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.android.kt
similarity index 93%
rename from mockzilla/src/androidUnitTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.kt
rename to mockzilla/src/androidUnitTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.android.kt
index 25bebb938..0b5a26113 100644
--- a/mockzilla/src/androidUnitTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.kt
+++ b/mockzilla/src/androidUnitTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.android.kt
@@ -1,3 +1,5 @@
+@file:JvmName("FileUtilsKt")
+
package com.apadmi.mockzilla.testutils
import java.io.File
diff --git a/mockzilla/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/discovery/ZeroConfDiscoveryService.kt b/mockzilla/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/discovery/ZeroConfDiscoveryService.kt
index ef14c7804..77e870583 100644
--- a/mockzilla/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/discovery/ZeroConfDiscoveryService.kt
+++ b/mockzilla/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/discovery/ZeroConfDiscoveryService.kt
@@ -4,7 +4,7 @@ import com.apadmi.mockzilla.lib.config.ZeroConfConfig
import com.apadmi.mockzilla.lib.models.MetaData
import com.apadmi.mockzilla.lib.models.RunTarget
-interface ZeroConfDiscoveryService {
+internal interface ZeroConfDiscoveryService {
suspend fun makeDiscoverable(metaData: MetaData, port: Int)
suspend fun stop()
}
diff --git a/mockzilla/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrorUtils.kt b/mockzilla/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrorUtils.kt
index 8a45a0f35..e60d889fe 100644
--- a/mockzilla/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrorUtils.kt
+++ b/mockzilla/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrorUtils.kt
@@ -2,9 +2,9 @@
package com.apadmi.mockzilla.lib.internal.utils
-expect class AddressAlreadyInUseException : Throwable
+internal expect class AddressAlreadyInUseException : Throwable
-fun Throwable.isSomeMatchInChain(predicate: (Throwable) -> Boolean): Boolean {
+internal fun Throwable.isSomeMatchInChain(predicate: (Throwable) -> Boolean): Boolean {
var current: Throwable? = this
while (current != null) {
if (predicate(current)) {
diff --git a/mockzilla/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/KtorMockzillaHttpRequest.kt b/mockzilla/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/KtorMockzillaHttpRequest.kt
index 1b2fd1bc5..9b75c7677 100644
--- a/mockzilla/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/KtorMockzillaHttpRequest.kt
+++ b/mockzilla/src/commonMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/KtorMockzillaHttpRequest.kt
@@ -7,7 +7,7 @@ import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
-class KtorMockzillaHttpRequest internal constructor(
+internal class KtorMockzillaHttpRequest(
private val call: ApplicationCall,
override val method: HttpMethod
) : MockzillaHttpRequest {
diff --git a/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.kt b/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.kt
index f733ff2c5..1b4eafbcc 100644
--- a/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.kt
+++ b/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.kt
@@ -1,3 +1,5 @@
+// This file intentionally breaks the convention and isn't named `Mockzilla.ios.kt` since
+// We want the Swift interop to expose MockzillaKt as it's type
package com.apadmi.mockzilla.lib
import com.apadmi.mockzilla.lib.internal.PlatformConfig
@@ -13,10 +15,10 @@ import com.apadmi.mockzilla.lib.models.PortConflictException
import kotlinx.coroutines.runBlocking
/**
- * Internal method to start the Mockzilla server. Consumer apps should prefer using the top-level
- * `startMockzilla()` function to avoid breaking changes.
+ * Starts the Mockzilla server.
*
* @param config The config with which to initialise mockzilla.
+ * @throws PortConflictException if the port specified in [config] is already in use.
*/
@Throws(PortConflictException::class)
fun startMockzilla(config: MockzillaConfig): MockzillaRuntimeParams = runBlocking {
@@ -37,9 +39,8 @@ fun startMockzilla(config: MockzillaConfig): MockzillaRuntimeParams = runBlockin
}
/**
- * Stops the Mockzilla server,
+ * Stops the running Mockzilla server.
*
- * @return
*/
actual fun stopMockzilla() = runBlocking {
stopServer()
diff --git a/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/discovery/ZeroConfDiscoveryServiceImpl.kt b/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/discovery/ZeroConfDiscoveryServiceImpl.kt
index 48023c695..012fc9cba 100644
--- a/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/discovery/ZeroConfDiscoveryServiceImpl.kt
+++ b/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/discovery/ZeroConfDiscoveryServiceImpl.kt
@@ -29,7 +29,7 @@ import kotlinx.cinterop.ptr
import kotlinx.cinterop.refTo
import kotlinx.cinterop.value
-class ZeroConfDiscoveryServiceImpl(
+internal class ZeroConfDiscoveryServiceImpl(
private val logger: Logger,
private val keychainSettings: KeychainSettings
) : ZeroConfDiscoveryService {
diff --git a/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/persistance/KeychainSettings.kt b/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/persistance/KeychainSettings.kt
index 6ce9b3354..6c84f19d3 100644
--- a/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/persistance/KeychainSettings.kt
+++ b/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/persistance/KeychainSettings.kt
@@ -93,7 +93,7 @@ import kotlinx.cinterop.value
* be used as the service name. It's also possible to pass custom key-value pairs as attributes that will be added to
* every key, if the default behavior does not fit your needs.
*/
-class KeychainSettings(vararg defaultProperties: Pair) {
+internal class KeychainSettings(vararg defaultProperties: Pair) {
@OptIn(ExperimentalForeignApi::class)
private val defaultProperties = mapOf(kSecClass to kSecClassGenericPassword) + mapOf(*defaultProperties)
diff --git a/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrorUtils.kt b/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrorUtils.kt
index ea73aca21..e82bb3521 100644
--- a/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrorUtils.kt
+++ b/mockzilla/src/iosMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrorUtils.kt
@@ -4,4 +4,4 @@ package com.apadmi.mockzilla.lib.internal.utils
import io.ktor.utils.io.errors.PosixException.AddressAlreadyInUseException as KtorAddressAlreadyInUseException
-actual typealias AddressAlreadyInUseException = KtorAddressAlreadyInUseException
+internal actual typealias AddressAlreadyInUseException = KtorAddressAlreadyInUseException
diff --git a/mockzilla/src/iosTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.kt b/mockzilla/src/iosTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.ios.kt
similarity index 100%
rename from mockzilla/src/iosTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.kt
rename to mockzilla/src/iosTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.ios.kt
diff --git a/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/JsMockzilla.kt b/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.js.kt
similarity index 97%
rename from mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/JsMockzilla.kt
rename to mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.js.kt
index f871d994a..e58e864b0 100644
--- a/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/JsMockzilla.kt
+++ b/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.js.kt
@@ -59,6 +59,9 @@ suspend fun startMockzilla(
}
}
+/**
+ * Stops the running Mockzilla server.
+ */
@OptIn(DelicateCoroutinesApi::class)
actual fun stopMockzilla() = GlobalScope.promise {
stopServer()
diff --git a/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/msw/MswBridge.kt b/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/msw/MswBridge.kt
index 2ca5618d1..8beb844f2 100644
--- a/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/msw/MswBridge.kt
+++ b/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/msw/MswBridge.kt
@@ -1,21 +1,25 @@
package com.apadmi.mockzilla.lib.internal.msw
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import org.w3c.fetch.Request
import org.w3c.fetch.Response
import kotlin.js.Promise
+@InternalMockzillaApi
@JsModule("msw/browser")
@JsNonModule
external object MswBrowser {
fun setupWorker(vararg handlers: RestHandler): ServiceWorkerInstance
}
+@InternalMockzillaApi
@JsModule("msw")
@JsNonModule
external object Msw {
val http: Rest
}
+@InternalMockzillaApi
external object Rest {
fun all(
path: String,
@@ -53,21 +57,25 @@ external object Rest {
): RestHandler
}
+@InternalMockzillaApi
external interface ResponseResolverInfo {
val request: Request
val requestId: String
}
+@InternalMockzillaApi
external interface DefaultContext {
fun status(status: Int): dynamic
fun json(body: Any): dynamic
fun text(body: String): dynamic
}
+@InternalMockzillaApi
external interface StartServiceWorkerOptions {
var onUnhandledRequest: String
}
+@InternalMockzillaApi
external interface ServiceWorkerInstance {
val context: ServiceWorkerContext
fun start(options: StartServiceWorkerOptions): Promise
@@ -76,8 +84,10 @@ external interface ServiceWorkerInstance {
fun stop(): Promise
}
+@InternalMockzillaApi
external interface ServiceWorkerContext {
val isMockingEnabled: Boolean
}
+@InternalMockzillaApi
external interface RestHandler
diff --git a/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrotUtils.kt b/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrotUtils.kt
index e009a8b10..6326aac25 100644
--- a/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrotUtils.kt
+++ b/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrotUtils.kt
@@ -4,7 +4,7 @@ package com.apadmi.mockzilla.lib.internal.utils
import kotlinx.io.IOException
-actual typealias AddressAlreadyInUseException = DummyException
+internal actual typealias AddressAlreadyInUseException = DummyException
// This will never actually happen since on JS multiple addresses aren't used
-class DummyException : IOException()
+internal class DummyException : IOException()
diff --git a/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/JsMockzillaRequest.kt b/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/JsMockzillaRequest.kt
index de33aa8e7..c15fadb67 100644
--- a/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/JsMockzillaRequest.kt
+++ b/mockzilla/src/jsMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/JsMockzillaRequest.kt
@@ -1,5 +1,6 @@
package com.apadmi.mockzilla.lib.internal.utils
+import com.apadmi.mockzilla.lib.InternalMockzillaApi
import com.apadmi.mockzilla.lib.models.MockzillaHttpRequest
import io.ktor.http.HttpMethod
@@ -11,6 +12,7 @@ import kotlinx.coroutines.await
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
+@InternalMockzillaApi
class JsMockzillaRequest(private val jsRequest: JsRequest) : MockzillaHttpRequest {
private val lock = Mutex()
private var bodyCache: String? = null
diff --git a/mockzilla/src/jsTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.kt b/mockzilla/src/jsTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.js.kt
similarity index 100%
rename from mockzilla/src/jsTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.kt
rename to mockzilla/src/jsTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.js.kt
diff --git a/mockzilla/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/JvmMockzilla.kt b/mockzilla/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.jvm.kt
similarity index 96%
rename from mockzilla/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/JvmMockzilla.kt
rename to mockzilla/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.jvm.kt
index 9c5ec4b0c..6879f7d34 100644
--- a/mockzilla/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/JvmMockzilla.kt
+++ b/mockzilla/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/Mockzilla.jvm.kt
@@ -1,3 +1,5 @@
+@file:JvmName("JvmMockzillaKt")
+
package com.apadmi.mockzilla.lib
import com.apadmi.mockzilla.BuildKonfig
@@ -56,9 +58,8 @@ fun startMockzilla(
}
/**
- * Stops the Mockzilla server,
+ * Stops the running Mockzilla server.
*
- * @return
*/
actual fun stopMockzilla() = runBlocking {
stopServer()
diff --git a/mockzilla/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrotUtils.kt b/mockzilla/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrotUtils.kt
index de31fa6de..12d602124 100644
--- a/mockzilla/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrotUtils.kt
+++ b/mockzilla/src/jvmMain/kotlin/com/apadmi/mockzilla/lib/internal/utils/ErrotUtils.kt
@@ -2,4 +2,4 @@
package com.apadmi.mockzilla.lib.internal.utils
-actual typealias AddressAlreadyInUseException = java.net.BindException
+internal actual typealias AddressAlreadyInUseException = java.net.BindException
diff --git a/mockzilla/src/jvmTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.kt b/mockzilla/src/jvmTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.jvm.kt
similarity index 91%
rename from mockzilla/src/jvmTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.kt
rename to mockzilla/src/jvmTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.jvm.kt
index bb81737b3..6bc9bf47e 100644
--- a/mockzilla/src/jvmTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.kt
+++ b/mockzilla/src/jvmTest/kotlin/com/apadmi/mockzilla/testutils/FileUtils.jvm.kt
@@ -1,3 +1,5 @@
+@file:JvmName("FileUtilsKt")
+
package com.apadmi.mockzilla.testutils
import java.io.File
diff --git a/samples/demo-android/src/main/res/xml/network_security_config.xml b/samples/demo-android/src/main/res/xml/network_security_config.xml
index 48c1dfd62..adf44a108 100644
--- a/samples/demo-android/src/main/res/xml/network_security_config.xml
+++ b/samples/demo-android/src/main/res/xml/network_security_config.xml
@@ -1,3 +1,4 @@
+
diff --git a/samples/demo-kmm/androidApp/src/main/AndroidManifest.xml b/samples/demo-kmm/androidApp/src/main/AndroidManifest.xml
index e6ef83c4a..f59d83555 100644
--- a/samples/demo-kmm/androidApp/src/main/AndroidManifest.xml
+++ b/samples/demo-kmm/androidApp/src/main/AndroidManifest.xml
@@ -8,6 +8,7 @@
android:allowBackup="false"
android:supportsRtl="true"
android:label="demo-mockzilla-kmm"
+ android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme">
+
+
+
+
+
+
+
+
+
+ localhost
+ 127.0.0.1
+
+
\ No newline at end of file