iamdevroyal / laravel-mobile-jump
Scan a QR code to instantly preview your Laravel app on any Android phone over local Wi-Fi
Package info
github.com/iamdevroyal/laravel-mobile-jump
pkg:composer/iamdevroyal/laravel-mobile-jump
Requires
- php: ^8.2
- bacon/bacon-qr-code: ^2.0|^3.0
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^11.0
README
Scan a QR code. See your Laravel app on your phone. Instantly.
Mobile Jump bridges the gap between your local Laravel development server and any Android device on the same Wi-Fi network. Run one command, scan a QR code with the companion app, and your phone loads your local Vue or React frontend in seconds โ with hot module replacement, API access, and a full native bridge for camera, microphone, location, biometrics, and more.
No tunnelling services. No internet required. No configuration headaches.
โจ Features
| Feature | Description |
|---|---|
| ๐ Zero-config auto-detection | Automatically detects your LAN IP โ just run and scan |
| ๐ฆ Universal | Works with any Laravel app and any frontend (Vue, React, Svelte, vanilla JS) |
| ๐๏ธ Pluggable storage | Redis (fastest), Database, or File fallback โ choose what fits your stack |
| ๐ฅ๏ธ Browser dashboard | Live session monitor at /mobile-jump/dashboard |
| ๐ฑ Android companion APK | Pre-built and bundled โ no Play Store, no build tools needed |
| ๐งฉ Frontend stubs | Ready-to-use Vue 3 composable and React hook |
| ๐ Native permissions | Camera, microphone, location, biometrics โ requested on demand |
| ๐ Rich native bridge | 20+ native Android capabilities accessible from JavaScript |
| โ Fully tested | PHPUnit + Orchestra Testbench test suite included |
๐ Requirements
- PHP 8.2+
- Laravel 11.x, 12.x, or 13.x
- Android phone on the same Wi-Fi network as your laptop
- One of: Redis (recommended), MySQL/SQLite, or a writable filesystem
- Vite (Vue/React frontend) configured to listen on
0.0.0.0โ see Frontend Setup
๐ Installation
Step 1 โ Install via Composer
composer require iamdevroyal/laravel-mobile-jump
The service provider is auto-discovered โ no manual registration needed.
Step 2 โ Run the Install Wizard
php artisan mobile:jump:install
The wizard will:
- โ
Publish
config/mobile-jump.php - โ
Copy the companion Android APK to
public/vendor/mobile-jump/MobileJump.apk - โ Check your configured storage backend (Redis / Database / File)
- โ Optionally scaffold a Vue 3 or React frontend stub
- โ
Automatically patch your
package.jsondev script andvite.config.jsto bind to0.0.0.0
Step 3 โ (Optional) Database Migration
Only needed if MOBILE_JUMP_STORAGE=database:
php artisan vendor:publish --tag=mobile-jump-migrations php artisan migrate
๐ Frontend Setup (Required)
Mobile Jump needs your Vite dev server to listen on all network interfaces so your Android device can reach it over Wi-Fi.
Note: The install wizard (
php artisan mobile:jump:install) attempts to do this automatically. Only follow these manual steps if you skipped the wizard or seeFrontend not reachablewarnings.
Vue 3 / Vite
Option A โ vite.config.js (recommended):
Add a server block to your vite.config.js:
// vite.config.js export default defineConfig({ // ... your existing config server: { host: '0.0.0.0', port: 5173, }, })
Then simply run:
npm run dev
Option B โ package.json dev script:
Update your package.json to include --host:
{
"scripts": {
"dev": "vite --host"
}
}
โ ๏ธ PowerShell users: Do NOT use
npm run dev -- --host 0.0.0.0. PowerShell passes0.0.0.0as a positional argument (root directory) rather than a--hostvalue. Use one of the options above instead.
React / Vite
Same as Vue โ add server.host: '0.0.0.0' to your vite.config.js:
// vite.config.js import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { host: '0.0.0.0', port: 5173, }, })
Next.js (React)
Next.js uses its own server. Run it with:
npx next dev --hostname 0.0.0.0
# or set it in next.config.js via experimental.serverComponentsExternalPackages
Then set MOBILE_JUMP_FRONTEND_PORT=3000 in your .env.
โก Avoiding "Axios Network Error" on Mobile Devices
When testing on an Android device via Mobile Jump, your web app loads at http://<your-lan-ip>:5173. If your API client hardcodes http://127.0.0.1:8000 or reads a .env file containing VITE_API_URL=http://127.0.0.1:8000, the mobile device will try to send API requests to 127.0.0.1:8000 (which points to the phone itself), causing an Axios Network Error.
Solution 1 โ Dynamic API Base URL Helper
In your primary API client file (e.g. src/api/client.js):
import axios from 'axios' export function getApiBaseUrl() { const envUrl = import.meta.env.VITE_API_URL // Use production domain directly if configured if (envUrl && !envUrl.includes('127.0.0.1') && !envUrl.includes('localhost')) { return envUrl } // Automatically use current LAN IP when accessed over Wi-Fi from a mobile device if (typeof window !== 'undefined' && window.location?.hostname) { const host = window.location.hostname if (host !== 'localhost' && host !== '127.0.0.1') { return `http://${host}:8000/api/v1` } } return envUrl || 'http://127.0.0.1:8000/api/v1' } const client = axios.create({ baseURL: getApiBaseUrl(), })
Solution 2 โ Dynamic Reverb / WebSockets Host
In your Echo / Reverb composable (e.g. src/composables/useReverb.js):
const envHost = import.meta.env.VITE_REVERB_HOST let activeHost = envHost || '127.0.0.1' if (typeof window !== 'undefined' && window.location?.hostname) { const host = window.location.hostname if (host !== 'localhost' && host !== '127.0.0.1' && (!envHost || envHost === '127.0.0.1' || envHost === 'localhost')) { activeHost = host } } const echo = new Echo({ broadcaster: 'reverb', wsHost: activeHost, // ... })
๐ฏ Usage
Start a Development Session
# 1. Start Laravel API on all interfaces: php artisan serve --host=0.0.0.0 # 2. Start your Vite frontend (--host baked in via install wizard or vite.config.js): npm run dev # 3. Start Mobile Jump: php artisan mobile:jump
Requires Vite configured for
host: '0.0.0.0'โ see Frontend Setup above.
A compact QR code renders in your terminal. You'll see something like:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ฑ Mobile Jump โ Dev Session โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Session: JMP-A4B2-9F1C TTL: 10 min โ
โ API: http://192.168.1.45:8000 โ
โ App: http://192.168.1.45:5173 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโ โโ โโ โโโ โโโ โโโ โโ โโโ โโโ ...
[QR code renders here in your terminal]
Waiting for device to connect...
Command Options
php artisan mobile:jump [options]
Options:
--host= Override the detected LAN IP address
--frontend-port= Vite dev server port (default: 5173)
--api-port= Laravel API port (default: 8000)
--ttl= Session lifetime in seconds (default: 600)
Browser Dashboard
Open the URL shown in the terminal banner, or navigate directly to:
http://<your-lan-ip>:8000/mobile-jump/dashboard?session=JMP-XXXX-XXXX
The dashboard shows live session status, connection time, device info, and a button to disconnect the session.
๐ฑ Android Companion App
The companion APK is bundled directly in the package. After installation it is copied to:
public/vendor/mobile-jump/MobileJump.apk
Transfer it to your phone:
- Share the file via Bluetooth, USB, or serve it locally:
http://<your-lan-ip>:8000/vendor/mobile-jump/MobileJump.apk - Install it (allow "Install from unknown sources" in Android settings)
On first launch, the app requests:
- Camera (for QR scanning and live capture)
- Microphone (for voice recording)
- Location (for geolocation sharing)
โ๏ธ Configuration
Publish the config file:
php artisan vendor:publish --tag=mobile-jump-config
config/mobile-jump.php:
return [ // URL prefix for all routes (e.g. /mobile-jump/api/connect) 'route_prefix' => env('MOBILE_JUMP_PREFIX', 'mobile-jump'), // Session lifetime in seconds 'session_ttl' => env('MOBILE_JUMP_TTL', 600), // Storage driver: 'redis' | 'database' | 'file' 'storage' => env('MOBILE_JUMP_STORAGE', 'redis'), // Redis connection name (from config/database.php) 'redis_connection' => env('MOBILE_JUMP_REDIS_CONNECTION', 'default'), // Whether to register routes automatically 'register_routes' => true, // Middleware applied to API routes 'middleware' => ['api'], // Override the APK download URL (null = use bundled APK) 'apk_url' => null, ];
Environment Variables
Add these to your .env as needed:
# Storage backend (redis is fastest, file works everywhere) MOBILE_JUMP_STORAGE=redis # Session TTL in seconds (default 10 minutes) MOBILE_JUMP_TTL=600 # Custom route prefix MOBILE_JUMP_PREFIX=mobile-jump
๐ Frontend Integration
Vue 3
After running php artisan mobile:jump:install and choosing Vue, stubs are placed in vendor-stubs/mobile-jump/. Copy them to your project:
// src/composables/useMobileJump.js import { useMobileJump } from '@/composables/useMobileJump' const { isRunner, isLan, apiBaseUrl, wsHost } = useMobileJump() // apiBaseUrl automatically resolves to: // - http://192.168.x.x:8000/api/v1 (when accessed from phone) // - http://127.0.0.1:8000/api/v1 (when on localhost)
React / TypeScript
// src/hooks/useMobileJump.ts import { useMobileJump } from './hooks/useMobileJump' const { isRunner, isLan, apiBaseUrl, wsHost } = useMobileJump()
Direct Bridge Access (Any Framework)
The companion app exposes a native bridge on window.MobileJumpNative:
const bridge = window.MobileJumpNative || window.KorpaBeeNative // null on desktop if (bridge) { // Device info const device = JSON.parse(bridge.getDeviceInfo()) // โ { platform: "android", model: "Samsung Galaxy S23", os_version: "14", runner_version: "v1.0.0" } // Haptic feedback bridge.vibrate(50) // Clipboard bridge.copyToClipboard('Hello from my app!') const text = bridge.readFromClipboard() // Toast notifications bridge.showToast('File uploaded!') // Native share sheet bridge.shareText('Check this out!', 'https://myapp.com/something') // Battery & network console.log('Battery:', bridge.getBatteryLevel() + '%') console.log('Online:', bridge.isNetworkAvailable()) // Location const loc = JSON.parse(bridge.getLastKnownLocation()) // โ { status: "ok", latitude: 6.5244, longitude: 3.3792, accuracy: 12.5, provider: "gps" } // Biometrics const bio = JSON.parse(bridge.getBiometricStatus()) // โ { available: true, status: "ready" } // Persistent key-value storage (survives page reloads) bridge.setStorageItem('user_prefs', JSON.stringify({ theme: 'dark' })) const prefs = JSON.parse(bridge.getStorageItem('user_prefs')) bridge.removeStorageItem('user_prefs') }
๐ Native Bridge โ Full Reference
All methods available at window.MobileJumpNative (also aliased as window.KorpaBeeNative):
Device & Platform
| Method | Returns | Description |
|---|---|---|
getDeviceInfo() |
string (JSON) |
Device metadata โ model, OS version, runner version |
getPlatform() |
"android" |
Platform identifier |
isRunnerEnvironment() |
"true" |
Feature detection flag |
Permissions
| Method | Returns | Description |
|---|---|---|
requestPermissions() |
void |
Triggers Android system permission dialog for camera, mic, and location |
hasCameraPermission() |
boolean |
Whether camera permission is currently granted |
hasAudioPermission() |
boolean |
Whether microphone permission is currently granted |
hasLocationPermission() |
boolean |
Whether location permission is currently granted |
Location
| Method | Returns | Description |
|---|---|---|
getLastKnownLocation() |
string (JSON) |
Last cached GPS/network location |
Biometrics
| Method | Returns | Description |
|---|---|---|
getBiometricStatus() |
string (JSON) |
Fingerprint/Face ID availability โ ready, not_enrolled, no_hardware |
Native UI
| Method | Returns | Description |
|---|---|---|
showToast(message) |
void |
Short Android Toast notification |
showToastLong(message) |
void |
Long Android Toast notification |
shareText(title, text) |
boolean |
Opens the native Android share sheet |
Clipboard
| Method | Returns | Description |
|---|---|---|
copyToClipboard(text) |
boolean |
Writes text to Android system clipboard |
readFromClipboard() |
string |
Reads current clipboard text |
Haptics
| Method | Returns | Description |
|---|---|---|
vibrate(milliseconds) |
void |
Haptic vibration (e.g. vibrate(40) for a button tap feel) |
Diagnostics
| Method | Returns | Description |
|---|---|---|
getBatteryLevel() |
string |
Battery percentage e.g. "85", or "-1" if unavailable |
isNetworkAvailable() |
"true" / "false" |
Whether any network connection is active |
Persistent Storage
| Method | Returns | Description |
|---|---|---|
setStorageItem(key, value) |
boolean |
Stores a string value in Android SharedPreferences |
getStorageItem(key) |
string |
Retrieves a stored value |
removeStorageItem(key) |
boolean |
Removes a stored value |
๐ API Endpoints
All endpoints are prefixed with the configured route_prefix (default: mobile-jump).
| Method | Endpoint | Description |
|---|---|---|
POST |
/mobile-jump/api/connect |
Device registers connection with session ID |
GET |
/mobile-jump/api/status/{id} |
Poll session status (waiting / connected) |
GET |
/mobile-jump/api/qr/{id} |
Returns the QR code as an SVG image |
DELETE |
/mobile-jump/api/disconnect/{id} |
Ends the session |
GET |
/mobile-jump/dashboard |
Browser-based session dashboard |
๐งช Testing
Run the included test suite:
composer install ./vendor/bin/phpunit
The tests use Orchestra Testbench and the File session store โ no Redis or database needed to run tests.
๐ ๏ธ How It Works
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Your Laptop โ
โ โ
โ php artisan serve --host=0.0.0.0 (port 8000) โ
โ npm run dev -- --host 0.0.0.0 (port 5173) โ
โ php artisan mobile:jump โ creates session โ
โ โ โ
โ Redis/DB/File โ stores session token + URLs โ
โ โ โ
โ QR Code rendered in terminal โ
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ scan
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Android Phone (same Wi-Fi) โ
โ โ
โ MobileJump.apk reads QR โ extracts JMP-XXXX-XXXX โ
โ POST /mobile-jump/api/connect โ registers device โ
โ WebView loads http://192.168.x.x:5173 โ
โ window.MobileJumpNative = NativeBridge instance โ
โ โ
โ Your Vue/React app runs natively with: โ
โ - Full HMR (live code reload) โ
โ - Real API access โ
โ - Camera, microphone, location, biometrics โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ฆ Changelog
See CHANGELOG.md for release history.
๐ค Contributing
Contributions, issues, and feature requests are welcome. Please open an issue first to discuss major changes.
๐ License
MIT โ see LICENSE.
๐ Credits
Built by iamdevroyal.
Inspired by the need to preview complex Laravel + Vue apps on real Android devices during development โ without the overhead of cloud tunnels, staging environments, or APK rebuilds.
u{1F4F1} Android App Repository
The companion Android app source code lives in its own repository: