@mostajs/browser-kit
v0.1.0
Published
Façade unifiée des Web APIs du navigateur (Wake Lock, Fullscreen, Visibility, Network, Web Share, Clipboard, Vibration, Screen Orientation…). Feature-detection + API promise + cleanup, dégradation gracieuse. ESM côté navigateur (se charge aussi sous Node
Maintainers
Readme
@mostajs/browser-kit
Auteur : Dr Hamid MADANI [email protected]
Façade unifiée des Web APIs du navigateur : une API cohérente, sûre et testable par-dessus une vingtaine de capacités natives. Membre de mosta-browser-stack.
Principes (chaque capacité respecte le même contrat) :
- Feature-detection —
isXSupported()oucapabilities()avant tout appel. - Promesses — pas de callbacks bruts ; les abonnements renvoient une fonction de désabonnement.
- Dégradation gracieuse — sur navigateur non compatible (ou sous Node : SSR/tests), rien ne jette :
false/null/ no-op. - Tree-shaking — import global ou par sous-chemin :
@mostajs/browser-kit/wake-lock. - Repli quand pertinent —
clipboard(execCommand),file-system-access(<input type=file>/<a download>).
npm i @mostajs/browser-kitDémarrage rapide
import { capabilities, createWakeLock, share, vibrate } from '@mostajs/browser-kit';
// 1) Que sait faire ce navigateur ?
console.log(capabilities());
// { wakeLock:true, fullscreen:true, share:true, clipboard:true, geolocation:true, … }
// 2) Empêcher la veille pendant une diffusion / lecture vidéo
const wl = createWakeLock({ onChange: (on) => console.log('écran maintenu allumé:', on) });
await wl.acquire();
wl.bindAutoReacquire(); // ré-acquiert tout seul au retour au premier plan
// … à la fin : await wl.dispose();
// 3) Partager + vibrer (mobile)
if (await share({ title: 'VoxStudio', url: location.href })) vibrate(50);Sous Node,
capabilities()renvoie tout àfalseet chaque appel dégrade sans erreur — le module est SSR-safe et testable.
How to use — recettes par cas d'usage
Garder l'écran allumé pendant un live (cas VoxStudio)
import { createWakeLock } from '@mostajs/browser-kit/wake-lock';
const wl = createWakeLock();
startPublishBtn.onclick = async () => { /* … getUserMedia + WHIP … */ await wl.acquire(); wl.bindAutoReacquire(); };
stopBtn.onclick = async () => { await wl.dispose(); };Bouton plein écran (avec icône qui suit l'état)
import { toggleFullscreen, onFullscreenChange } from '@mostajs/browser-kit/fullscreen';
btn.onclick = () => toggleFullscreen(document.getElementById('player'));
onFullscreenChange((on) => btn.textContent = on ? '⤡ Quitter' : '⤢ Plein écran');Mettre l'app en pause quand l'onglet est caché (économie CPU/batterie)
import { onHidden, onVisible } from '@mostajs/browser-kit/visibility';
onHidden(() => player.pause());
onVisible(() => player.play());Bandeau « hors-ligne » + adaptation à la qualité réseau
import { onOffline, onOnline, connectionInfo } from '@mostajs/browser-kit/network';
onOffline(() => banner.show('Hors-ligne — reconnexion…'));
onOnline(() => banner.hide());
const net = connectionInfo(); // { effectiveType:'4g', downlink:10, saveData:false }
if (net?.saveData || net?.effectiveType === '2g') useLowQuality();Copier un lien d'invitation (repli automatique en HTTP)
import { writeText } from '@mostajs/browser-kit/clipboard';
copyBtn.onclick = async () => { if (await writeText(inviteUrl)) toast('Lien copié ✓'); };Notifier + pastille d'icône (PWA) à l'arrivée d'une question Q&A
import { notify } from '@mostajs/browser-kit/notifications';
import { setBadge } from '@mostajs/browser-kit/badging';
await notify('Nouvelle question', { body: q.text, icon: '/logo.png' });
await setBadge(unread);Position de l'utilisateur (carte d'un événement)
import { getCurrentPosition } from '@mostajs/browser-kit/geolocation';
const pos = await getCurrentPosition(); // { lat, lng, accuracy } ou null si refusé
if (pos) centerMap(pos.lat, pos.lng);Choisir caméra/micro + partage d'écran (plateau)
import { enumerateDevices, getUserMedia, getDisplayMedia, stopStream } from '@mostajs/browser-kit/media-devices';
const { cameras, microphones } = await enumerateDevices();
const cam = await getUserMedia({ video: { deviceId: cameras[0]?.deviceId }, audio: true });
const screen = await getDisplayMedia(); // partage d'écran
// … stopStream(cam) à l'arrêtBip de notification synthétisé (sans fichier audio)
import { beep, playSequence } from '@mostajs/browser-kit/audio';
beep({ freq: 880, duration: 120 }); // après un geste utilisateur
playSequence([{ freq: 660, duration: 100 }, { freq: 990, duration: 140 }]);Référence des capacités
capabilities() → carte booléenne de tout ce qui suit (21 clés).
Lot 1
| Module / sous-chemin | Exports |
|---|---|
| wake-lock | isWakeLockSupported() · createWakeLock({onChange}) → { isSupported, acquire(), release(), isActive(), bindAutoReacquire()→off, dispose() } |
| fullscreen | isFullscreenSupported() · isFullscreen() · requestFullscreen(el?) · exitFullscreen() · toggleFullscreen(el?) · onFullscreenChange(cb)→off |
| visibility | isVisibilitySupported() · isVisible() · onVisibilityChange(cb)→off · onVisible(cb) · onHidden(cb) |
| network | isOnline() · isNetworkInfoSupported() · connectionInfo() → {effectiveType,downlink,rtt,saveData,type} · onNetworkChange(cb) · onOnline(cb) · onOffline(cb) · onConnectionChange(cb) |
| web-share | isShareSupported() · canShareFiles() · canShare(data) · share({title,text,url,files}) → bool |
| clipboard | isClipboardSupported() · writeText(s) → bool (repli execCommand) · readText() → string|null |
| vibration | isVibrationSupported() · vibrate(pattern) · stopVibration() |
| screen-orientation | isOrientationSupported() · currentOrientation() → {type,angle} · lockOrientation(type) · unlockOrientation() · onOrientationChange(cb)→off |
Lot 2
| Module / sous-chemin | Exports |
|---|---|
| geolocation | isGeolocationSupported() · getCurrentPosition(opts) → {lat,lng,accuracy,altitude,heading,speed,timestamp}|null · watchPosition(cb,opts)→off |
| notifications | isNotificationSupported() · notificationPermission() · requestNotificationPermission() → état · notify(title,opts) → Notification|null |
| permissions | isPermissionsSupported() · queryPermission(name) → 'granted'\|'denied'\|'prompt'|null · onPermissionChange(name,cb)→off |
| battery | isBatterySupported() · getBattery() → {level,charging,chargingTime,dischargingTime}|null · onBatteryChange(cb)→off |
| idle-detection | isIdleDetectionSupported() · requestIdlePermission() · createIdleDetector({threshold,onChange}) → {start(),stop()} |
| media-devices | isMediaDevicesSupported() · isDisplayMediaSupported() · enumerateDevices() → {cameras,microphones,speakers} · getUserMedia(c) · getDisplayMedia(c) · stopStream(s) · onDeviceChange(cb)→off |
| sensors | isOrientationSensorSupported() · isMotionSensorSupported() · requestSensorPermission() (iOS 13+) · onDeviceOrientation(cb)→off · onDeviceMotion(cb)→off |
| audio | isAudioSupported() · beep({freq,duration,type,volume}) → bool · playSequence([{freq,duration,gap}]) (Web Audio, sans fichier) |
| badging | isBadgingSupported() · setBadge(count?) · clearBadge() (pastille icône PWA) |
| file-system-access | isFileSystemAccessSupported() · openFile(opts) → File|null (repli <input file>) · saveFile(blob,opts) → bool (repli <a download>) |
| contact-picker | isContactPickerSupported() · pickContacts(props,opts) → contacts|null · availableContactProperties() |
Notes & limites
- Contexte sécurisé (HTTPS) requis par la plupart des API (Wake Lock, Clipboard async, getUserMedia, Geolocation…).
- Geste utilisateur requis pour : Fullscreen, Web Share, déblocage du contexte Audio, permission capteurs iOS.
- Permissions : Geolocation, Notifications, Camera/Micro, Idle Detection demandent l'accord de l'utilisateur — vérifier l'état via
permissions. - Disponibilité variable :
idle-detection,badging,contact-picker,file-system-access, Network Information sont surtout sur Chromium. Toujours tester viacapabilities()et prévoir le repli.
Tests
npm test — 6 tests (@mostajs/mjs-unit) : dégradé sous Node + navigateur simulé (globalThis.navigator/Notification).
Licence : AGPL-3.0-or-later.
