Claude Code Plugins

Community-maintained marketplace

Feedback

NativePHP JavaScript Integration

@NativePHP/ClaudePlugins
2
0

This skill should be used when the user asks about "javascript nativephp", "vue nativephp", "react nativephp", "#nativephp import", "Native.on", "js native function", "javascript camera", "javascript scanner", "inertia nativephp", "svelte nativephp", "frontend native api", or needs to use NativePHP native functionality in JavaScript/Vue/React applications.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name NativePHP JavaScript Integration
description This skill should be used when the user asks about "javascript nativephp", "vue nativephp", "react nativephp", "#nativephp import", "Native.on", "js native function", "javascript camera", "javascript scanner", "inertia nativephp", "svelte nativephp", "frontend native api", or needs to use NativePHP native functionality in JavaScript/Vue/React applications.
version 0.1.0

NativePHP JavaScript Integration

This skill provides guidance for using NativePHP native functionality in JavaScript, Vue, React, Svelte, and other frontend frameworks.

Overview

NativePHP provides a JavaScript library that mirrors the PHP APIs, enabling frontend frameworks to access native device functionality. The library is fully typed for excellent IDE support.

Installation

Add the import map to your package.json:

{
  "imports": {
    "#nativephp": "./vendor/nativephp/mobile/resources/dist/native.js"
  }
}

Or in Vite config:

// vite.config.js
export default defineConfig({
  resolve: {
    alias: {
      '#nativephp': './vendor/nativephp/mobile/resources/dist/native.js'
    }
  }
});

Basic Usage

import { camera, dialog, scanner, Events, on, off } from '#nativephp';

// Take a photo
camera.getPhoto().id('my-photo').start();

// Show an alert
dialog.alert('Title', 'Message', ['OK', 'Cancel']).show();

// Listen for events
on(Events.Camera.PhotoTaken, handlePhotoTaken);

Event Handling

Subscribing to Events

import { on, off, Events } from '#nativephp';

// Define handler as named function for cleanup
const handlePhotoTaken = (path, mimeType, id) => {
    console.log('Photo taken:', path);
};

// Subscribe to an event
on(Events.Camera.PhotoTaken, handlePhotoTaken);

// Later, unsubscribe using off() with the same handler reference
off(Events.Camera.PhotoTaken, handlePhotoTaken);

Important: on() does NOT return an unsubscribe function. You must use off() with the same handler reference to unsubscribe.

Available Events

import { Events } from '#nativephp';

// Camera events
Events.Camera.PhotoTaken
Events.Camera.PhotoCancelled
Events.Camera.VideoRecorded
Events.Camera.VideoCancelled

// Gallery events
Events.Gallery.MediaSelected

// Scanner events
Events.Scanner.CodeScanned

// Biometric events
Events.Biometric.Completed

// Dialog events
Events.Alert.ButtonPressed

// Microphone events
Events.Microphone.MicrophoneRecorded
Events.Microphone.MicrophoneCancelled

// Geolocation events
Events.Geolocation.LocationReceived
Events.Geolocation.PermissionStatusReceived
Events.Geolocation.PermissionRequestResult

// Push notification events
Events.PushNotification.TokenGenerated

Camera API

import { camera, Events, on } from '#nativephp';

// Take a photo
camera.getPhoto()
    .id('avatar')
    .remember()
    .start();

// Record video with max duration
camera.recordVideo()
    .maxDuration(30)
    .id('clip')
    .start();

// Pick from gallery
camera.pickImages()
    .images()           // or .videos() or .all()
    .multiple(true, 5)  // Allow up to 5 selections
    .id('gallery-pick')
    .start();

// Handle results
on(Events.Camera.PhotoTaken, (path, mimeType, id) => {
    if (id === 'avatar') {
        document.getElementById('avatar-img').src = path;
    }
});

on(Events.Gallery.MediaSelected, (success, files, count, error, cancelled, id) => {
    if (success) {
        files.forEach(file => console.log(file.path));
    }
});

Scanner API

import { scanner, Events, on } from '#nativephp';

// Start scanning
scanner.scan()
    .prompt('Scan QR code')
    .formats(['qr', 'ean13'])
    .continuous(true)
    .id('product-scan')
    .scan();

// Handle scanned codes
on(Events.Scanner.CodeScanned, (data, format, id) => {
    console.log(`Scanned ${format}: ${data}`);
});

Biometrics API

import { biometrics, Events, on } from '#nativephp';

// Prompt for authentication
biometrics.prompt()
    .id('secure-action')
    .prompt();

// Handle result
on(Events.Biometric.Completed, (success, id) => {
    if (success) {
        performSecureAction();
    } else {
        showAuthError();
    }
});

Dialog API

import { dialog, Events, on } from '#nativephp';

// Show alert with buttons
dialog.alert('Confirm', 'Delete this item?', ['Cancel', 'Delete'])
    .id('confirm-delete')
    .show();

// Handle button press
on(Events.Alert.ButtonPressed, (index, label, id) => {
    if (id === 'confirm-delete' && label === 'Delete') {
        deleteItem();
    }
});

// Show toast (no event)
dialog.toast('Item saved', 'short');

Geolocation API

import { geolocation, Events, on } from '#nativephp';

// Check permissions
geolocation.checkPermissions().get();

// Request permissions
geolocation.requestPermissions().get();

// Get current position
geolocation.getCurrentPosition()
    .fineAccuracy(true)
    .id('location')
    .get();

// Handle results
on(Events.Geolocation.LocationReceived, (success, lat, lng, accuracy, timestamp, provider, error, id) => {
    if (success) {
        console.log(`Location: ${lat}, ${lng}`);
    }
});

on(Events.Geolocation.PermissionStatusReceived, (location, coarse, fine, id) => {
    console.log(`Fine location permission: ${fine}`);
});

Microphone API

import { microphone, Events, on } from '#nativephp';

// Start recording
microphone.record()
    .id('voice-note')
    .start();

// Control recording
microphone.pause();
microphone.resume();
microphone.stop();

// Get status
const status = await microphone.getStatus(); // 'idle', 'recording', 'paused'
const path = await microphone.getRecording();

// Handle completion
on(Events.Microphone.MicrophoneRecorded, (path, mimeType, id) => {
    console.log('Recording saved:', path);
});

Other APIs

import { device, browser, share, secureStorage, network, system, pushNotifications } from '#nativephp';

// Device
const deviceId = await device.getId();
const info = await device.getInfo();
device.vibrate();
device.flashlight();

// Browser
browser.open('https://example.com');
browser.inApp('https://example.com');
browser.auth('https://oauth.example.com');

// Share
share.url('Check this out', 'Description', 'https://example.com');
share.file('Document', 'Here is the file', '/path/to/file.pdf');

// Secure Storage
await secureStorage.set('token', 'secret-value');
const token = await secureStorage.get('token');
await secureStorage.delete('token');

// Network
const status = await network.status();
console.log(status.connected, status.type);

// System
if (system.isMobile()) { /* mobile-specific code */ }
if (system.isIos()) { /* iOS-specific code */ }
if (system.isAndroid()) { /* Android-specific code */ }
system.appSettings();

// Push Notifications
pushNotifications.enroll().id('main').enroll();
const pushToken = await pushNotifications.getToken();

Vue 3 Example

<template>
  <div class="photo-uploader">
    <img v-if="photoUrl" :src="photoUrl" alt="Photo" />
    <button @click="takePhoto">Take Photo</button>
    <button @click="pickPhoto">Choose from Gallery</button>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { camera, Events, on, off } from '#nativephp';

const photoUrl = ref(null);

const takePhoto = () => {
  camera.getPhoto()
    .id('vue-photo')
    .start();
};

const pickPhoto = () => {
  camera.pickImages()
    .images()
    .single()
    .id('vue-gallery')
    .start();
};

// Define handlers as named functions for cleanup
const handlePhotoTaken = (path, mimeType, id) => {
  photoUrl.value = path;
};

const handleMediaSelected = (success, files, count, error, cancelled, id) => {
  if (success && files.length > 0) {
    photoUrl.value = files[0].path;
  }
};

onMounted(() => {
  on(Events.Camera.PhotoTaken, handlePhotoTaken);
  on(Events.Gallery.MediaSelected, handleMediaSelected);
});

onUnmounted(() => {
  off(Events.Camera.PhotoTaken, handlePhotoTaken);
  off(Events.Gallery.MediaSelected, handleMediaSelected);
});
</script>

React Example

import { useState, useEffect } from 'react';
import { camera, scanner, Events, on, off } from '#nativephp';

function QRScanner() {
  const [scannedData, setScannedData] = useState([]);

  useEffect(() => {
    // Define handler for cleanup reference
    const handleCodeScanned = (data, format, id) => {
      setScannedData(prev => [...prev, { data, format }]);
    };

    on(Events.Scanner.CodeScanned, handleCodeScanned);

    // Cleanup: use off() with same handler reference
    return () => off(Events.Scanner.CodeScanned, handleCodeScanned);
  }, []);

  const startScan = () => {
    scanner.scan()
      .prompt('Scan barcode')
      .formats(['qr', 'ean13', 'code128'])
      .continuous(true)
      .scan();
  };

  return (
    <div>
      <button onClick={startScan}>Start Scanning</button>
      <ul>
        {scannedData.map((item, i) => (
          <li key={i}>{item.format}: {item.data}</li>
        ))}
      </ul>
    </div>
  );
}

export default QRScanner;

Inertia.js Example

When using Inertia with Vue/React, EDGE components (TopBar, BottomNav, etc.) go in your app.blade.php layout file, not in Vue components.

resources/views/app.blade.php:

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
    @vite(['resources/js/app.js'])
    @inertiaHead
</head>
<body class="nativephp-safe-area">
    {{-- EDGE components go here in the Blade layout --}}
    <native:top-bar title="My App" />

    @inertia

    <native:bottom-nav>
        <native:bottom-nav-item id="home" icon="home" label="Home" :url="route('home')" />
        <native:bottom-nav-item id="profile" icon="person" label="Profile" :url="route('profile')" />
    </native:bottom-nav>
</body>
</html>

Vue Component (resources/js/Pages/SecureForm.vue):

<template>
  <main class="p-4">
    <button @click="authenticateAndSubmit">Submit Securely</button>
  </main>
</template>

<script setup>
import { onMounted, onUnmounted } from 'vue';
import { router } from '@inertiajs/vue3';
import { biometrics, Events, on, off } from '#nativephp';

const formData = { /* your form data */ };

const authenticateAndSubmit = () => {
  biometrics.prompt().id('form-submit').prompt();
};

const handleBiometricCompleted = (success, id) => {
  if (success && id === 'form-submit') {
    router.post('/submit-form', formData);
  }
};

onMounted(() => {
  on(Events.Biometric.Completed, handleBiometricCompleted);
});

onUnmounted(() => {
  off(Events.Biometric.Completed, handleBiometricCompleted);
});
</script>

Safe Area in JavaScript Apps

Apply safe area handling:

<body class="nativephp-safe-area">
  <div id="app"></div>
</body>

Or use CSS variables:

.my-header {
  padding-top: calc(16px + var(--inset-top));
}

.my-footer {
  padding-bottom: calc(16px + var(--inset-bottom));
}

TypeScript Support

The library is fully typed:

import { camera, Events, on, PhotoTakenEvent } from '#nativephp';

const handlePhoto = (path: string, mimeType: string, id: string | null) => {
  // Fully typed parameters
};

on(Events.Camera.PhotoTaken, handlePhoto);

Best Practices

  1. Clean up event listeners with off() - Always use off() with the same handler reference in component cleanup:

    // Vue
    onMounted(() => on(Events.Camera.PhotoTaken, handlePhoto));
    onUnmounted(() => off(Events.Camera.PhotoTaken, handlePhoto));
    
    // React
    useEffect(() => {
      const handler = (path) => setPhoto(path);
      on(Events.Camera.PhotoTaken, handler);
      return () => off(Events.Camera.PhotoTaken, handler);
    }, []);
    
  2. Use IDs for tracking - Correlate requests with responses when multiple operations may be in flight

  3. Builders are thenable - All Pending* builders can be awaited directly without calling .start(), .scan(), etc:

    // Both work:
    await camera.getPhoto().id('test').start();  // Explicit
    await camera.getPhoto().id('test');          // Implicit (thenable)
    
  4. Check for mobile context - Use system.isMobile() before calling native APIs

  5. Graceful degradation - Provide fallbacks for web-only testing

  6. EDGE components in Blade - When using Inertia/Vue/React, EDGE components (TopBar, BottomNav, etc.) must go in app.blade.php, not in JS components

Fetching Live Documentation

For detailed JavaScript integration:

  • Native Functions: https://nativephp.com/docs/mobile/2/the-basics/native-functions
  • Events: https://nativephp.com/docs/mobile/2/the-basics/events

Use WebFetch to retrieve the latest JavaScript patterns and API details.