protendai/nativephp-wifi-direct
This is a first public release (v1.0.0, MIT), so there is no upgrade path to worry about — but also no battle-testing behind it. Treat it as a young package and budget time for device testing on real hardware rather than simulators.
resources/js/index.js) with your app assets. Native sources now live under resources/android/src and resources/ios/Sources — if you have a custom native build step, point it there. Follow the README's install section for the exact commands; it's the authoritative source for this release.NSLocalNetworkUsageDescription is declared by the plugin, and Android permissions are requested at runtime. Verify what actually lands in the merged manifest / Info.plist of your app shell, since host apps often override these.WifiDirect::isValidNetworkName() / WifiDirect::isValidPassphrase(), calls createGroup(), renders a QR pairing code, and listens for Connected. A guest scans the code, hands the payload to parsePairing(), and calls connect().Start in PHP only. Get a host/join round trip working through the facade and Laravel events before you add the Inertia/Vue/React layer — it isolates whether a failure is native, PHP, or front-end.
Validate credentials in PHP before you call native. The headline Android fix is that malformed credentials crashed the app inside WifiP2pConfig.Builder. The package now validates in PHP, JS and natively, but you still want the PHP check for UX: reject bad input with a form error instead of a silent false from connect().
$name = trim($request->input('network_name'));
if (! WifiDirect::isValidNetworkName($name) || ! WifiDirect::isValidPassphrase($request->input('passphrase'))) {
// return validation error, never reach the bridge
}
Treat connect()'s bool as the primary result, events as follow-up. connect() now returns false — with no event — for malformed credentials or when the device refuses the call. So:
if (! WifiDirect::connect($ssid, $passphrase)) {
return back()->withErrors(['pairing' => 'This device refused the connection request.']);
}
// only now start waiting for Connected / ConnectionFailed
Model host and join as two explicit flows, not one generic "sync" method. Because createGroup() and connect() cancel each other, an accidental double-invocation (a double-tap, a retried request, a Livewire re-render) will tear down the flow you just started. Put each flow behind a single action class or service method and guard with a state machine (idle → hosting/joining → connected), ignoring calls unless state is idle.
Handle parsePairing() returning null. Codes whose network name or passphrase are malformed return null rather than throwing. Always branch:
$pair = WifiDirect::parsePairing($scanned);
if ($pair === null) {
return back()->withErrors(['qr' => 'That QR code is not a valid pairing code.']);
}
Listen on both sides of the stack. PHP events (Connected, ConnectionFailed, Disconnected, PermissionResult, QR scan requests) drive server state; the JS library (Events names, onWifiDirect()) drives the UI. Keep the two thin: the JS side should re-render, the PHP listener should persist. Use the JS Events constants rather than string literals so a rename doesn't silently break listeners.
Permissions are a first-class flow, not a preamble. PermissionResult now carries reason (denied, permanently denied, approximate, interrupted) and a permanentlyDenied flag. The pattern that holds up:
denied → explain and offer retry.permanently denied / permanentlyDenied → stop asking, point the user at system settings.approximate (Android 12/12L) → this is the fixed case where precise was requested alone and the permission was silently denied; ask for both, and don't block the UI on a "precise" assumption.interrupted → the request survived activity recreation but did not resolve; re-request on next interaction rather than looping.Test with the FakeBridge, and use the new macros to pin intent. withWifiDirect() now also answers connect(), and the new assertions let you test the flow rather than the implementation:
WifiDirect::fake()->withWifiDirect();
$this->post('/group', ['name' => 'DIRECT-myApp', 'passphrase' => 'correct horse battery'])
->assertRedirect();
WifiDirect::assertWifiDirectHosting();
Pair assertWifiDirectHosting() / assertWifiDirectJoining() / assertWifiDirectDisconnected() with assertQrScanRequested() for QR-driven join screens. Assert the negative too: that a malformed pairing code never reaches connect().
Keep pairing secrets out of persistence. Network names are no longer written to the log, but the passphrase and the QR payload still are secrets. Don't put them in session flashes, debug dumps, error tracking breadcrumbs, or Inertia props you don't need.
disconnect() always emits Disconnected, on both platforms, even with no link. Your listener must be idempotent. Don't use the event to infer "there was a connection" — drive that from your own state.Disconnected can arrive before Connected. Replacing an in-flight or reported link fires Disconnected for the old one first. If your UI treats Disconnected as terminal, a fast host→join transition will look like a failure. Sequence-check with a flow/generation ID.Connected after ConnectionFailed is fixed but conceptually still possible on real radios. The Android join is now cancelled and a late-forming group removed — still, don't write listeners that assume strict event ordering without a timeout. Add your own deadline (a queued job or a timer in JS) around every join/host attempt.status() is advisory. A first-call enabled: false was a real bug and is fixed by reading sticky P2P state on attach, but status() still can't tell you whether permissions are granted. Gate your UI on permission results and status, not status alone.NSLocalNetworkUsageDescription, but if the user declines, joins simply won't complete. NWPathMonitor plus an address poll now report the drop — which also means detection is not instantaneous. Don't poll aggressively to compensate; it's the package's job and doubling up wastes battery.resources/android/src and resources/ios/Sources are the new native locations. Any custom native patch or build script you wrote against the previous layout needs updating. Also note iOS minimum is now 15.0 — confirm your deployment target.PairingService) so you can swap in a fake, add your own timeouts/state machine, and keep controllers free of event-listener registration. The package's Laravel Boost guidelines and Pest suite are useful references for the intended idiom — read them before inventing your own wrapper.How can I help you explore Laravel packages today?