Project Fugu
When you build for native, you have choice.
When you build for web, your options are limited.
Users want these features
So developers flock to solutions other than the Web
Mobile hybrid app frameworks like Cordova
Super app platforms for mini apps like WeChat
Desktop hybrid app frameworks like Electron
Audio & Video Capture
Advanced Camera Controls
Recording Media
Real-Time Communication
USB
Credentials
Payments
Network Type & Speed
Online State
Vibration
Battery Status
Local Notifications
Push Messages
Home Screen Installation
Foreground Detection
Permissions
File Reading
Touch Settings
Speech Recognition
Pointing Device Adaptation
Offline Mode
Background Sync
Geolocation
Device Position
Device Motion
Virtual & Augmented Reality
Fullscreen
Screen Orientation & Lock
Presentation Features
Vibration
Device Memory
Bluetooth
Real-Time Communication
Touch Gestures
Storage Quotas
Audio & Video Capture
Advanced Camera Controls
Recording Media
Real-Time Communication
USB
Credentials
Payments
Network Type & Speed
Online State
Vibration
Battery Status
Local Notifications
Push Messages
Home Screen Installation
Foreground Detection
Permissions
File Reading
Contacts
SMS
Serial
Task Scheduling
Touch Settings
Speech Recognition
Offline Mode
Background Sync
Inter-App Communication
NFC
Geolocation
Geofencing
Device Position
Device Motion
Wake Lock
Proximity Sensors
Ambient Light
Virtual & Augmented Reality
Fullscreen
Screen Orientation & Lock
Presentation Features
Task Scheduling
Vibration
Device Memory
Bluetooth
Touch Gestures
Run on Startup
Clipboard Access
Unreliable Socket Communications
Storage Quotas
Clipboard Access
Unlimited Storage
WebHID (HID)
App Icon Badging
Local Fonts Access
Real-Time Communication
Pointing Device Adaptation
Enable web apps to do anything native apps can, by exposing the capabilities of native platforms to the web platform, while maintaining user security, privacy, trust, and other core tenets of the web.
goo.gle/project-fugu
bit.ly/powerful-apis
From idea to new API
From idea to new API
Identify Need
From idea to new API
Write Explainer
From idea to new API
Solicit Feedback
From idea to new API
Formalize Spec
From idea to new API
Formalize Spec
From idea to new API
Ship It
bit.ly/fugu-bugs
goo.gle/fugu-api-tracker
Resources
Web Capabilities page:�goo.gle/capabilities
Fugu Capability process:�goo.gle/fugu-process
Fugu Greetings
The Native File System API
const importImage = async () => {
try {
const handle = await window.showOpenFilePicker();
return handle.getFile();
} catch (err) {
console.error(err.name, err.message);
}
};
const exportImage = async (blob) => {
try {
const handle = await window.showSaveFilePicker();
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
} catch (err) {
console.error(err.name, err.message);
}
};
The Contact Picker API
const getContacts = async () => {
const properties = ['name'];
const options = {multiple: true};
try {
return await navigator.contacts.select(properties, options);
} catch (err) {
console.error(err.name, err.message);
}
};
if ('contacts' in navigator) {
import('./contacts.mjs');
}
The Asynchronous Clipboard API
const copy = async (blob) => {
try {
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
} catch (err) {
console.error(err.name, err.message);
}
};
const paste = async () => {
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
try {
for (const type of clipboardItem.types) {
const blob = await clipboardItem.getType(type);
return blob;
}
} catch (err) {
console.error(err.name, err.message);
}
}
} catch (err) {
console.error(err.name, err.message);
}
};
if (('clipboard' in navigator) &&
('write' in navigator.clipboard)) {
import('./clipboard.mjs');
}
The Badging API
let strokes = 0;
canvas.addEventListener('pointerdown', () => {
navigator.setAppBadge(++strokes);
});
clearButton.addEventListener('click', () => {
strokes = 0;
navigator.setAppBadge(strokes);
});
if ('setAppBadge' in navigator) {
import('./badge.mjs');
}
The Periodic Background Sync API
const registerPeriodicBackgroundSync = async () => {
const registration = await navigator.serviceWorker.ready;
try {
registration.periodicSync.register('image-of-the-day-sync', {
// An interval of one day.
minInterval: 24 * 60 * 60 * 1000,
});
} catch (err) {
console.error(err.name, err.message);
}
};
self.addEventListener('periodicsync', (syncEvent) => {
if (syncEvent.tag === 'image-of-the-day-sync') {
syncEvent.waitUntil((async () => {
const blob = await getImageOfTheDay();
const clients = await self.clients.matchAll();
clients.forEach((client) => {
client.postMessage({
image: blob,
});
});
})());
}
});
// In the client:
const registration = await navigator.serviceWorker.ready;
if (registration && 'periodicSync' in registration) {
import('./periodic_background_sync.mjs');
}
// In the service worker:
if ('periodicSync' in self.registration) {
importScripts('./image_of_the_day.mjs');
}
Notification Triggers API
const targetDate = promptTargetDate();
if (targetDate) {
const registration = await navigator.serviceWorker.ready;
registration.showNotification('Reminder', {
tag: 'reminder',
body: 'It’s time to finish your greeting card!',
showTrigger: new TimestampTrigger(targetDate),
});
}
if (('Notification' in window) &&
('showTrigger' in Notification.prototype)) {
import('./notification_triggers.mjs');
}
The Wake Lock API
let wakeLock = null;
const requestWakeLock = async () => {
wakeLock = await navigator.wakeLock.request('screen');
wakeLock.addEventListener('release', () => {
console.log('Wake Lock was released');
});
console.log('Wake Lock is active');
};
const handleVisibilityChange = () => {
if (wakeLock !== null && document.visibilityState === 'visible') {
requestWakeLock();
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener('fullscreenchange', handleVisibilityChange);
if (('wakeLock' in navigator) &&
('request' in navigator.wakeLock)) {
import('./wake_lock.mjs');
}
The Idle Detection API
const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
const userState = idleDetector.userState;
const screenState = idleDetector.screenState;
console.log(`Idle change: ${userState}, ${screenState}.`);
if (userState === 'idle') {
clearCanvas();
}
});
await idleDetector.start({
threshold: 60000,
signal,
});
if ('IdleDetector' in window) {
import('./idle_detection.mjs');
}
Project Fugu and You
Thank you!