Device & platform APIs
Use browser device APIs responsibly: clipboard, permissions, geolocation, notifications, and audio or video controls that depend on user trust.
- 01Ask at the right timeUse feature detection and request permissions only after a user action.
- 02Handle refusalMap permission states and geolocation errors to useful UI instead of treating no as failure.
- 03Control mediaUse
HTMLMediaElementpromises, events, volume, and playback rate safely.
Why device APIs feel different
Most JavaScript you have written so far changes data, the DOM, forms, storage, or network requests. Device and platform APIs cross a more personal boundary: they can read the clipboard, ask where the device is, show system notifications, or start media through speakers and screens. That is useful, but it also touches privacy and attention.
The professional pattern is simple: check support in the real browser, explain the benefit, start from a user action, handle no, and keep a fallback. This lesson uses the same event skills from Introduction to events, the promise skills from Promises, and the Blob idea from the previous Files & Blobs lesson.
A hotel does not hand every guest a master key at check-in. Each door has a purpose. Browser permissions are the same kind of trust boundary: each capability is separate, and the person using the browser may say no.
- In real life: One card opens the gym
- In JavaScript: A location grant lets the site use geolocation
- In real life: Another door still stays locked
- In JavaScript: Location permission does not grant notifications or clipboard reads
- In real life: The guest can refuse the card
- In JavaScript: The user can choose
deniedor ignore the prompt - In real life: Staff should explain why they need access
- In JavaScript: Good UI asks only when the feature is requested
Where the analogy stops: A hotel card is usually physical and stable. Browser permissions vary by browser, origin, device policy, private mode, and time, so always check the real feature too.
Every experiment in this lesson starts from a button, paste event, or media control. Nothing asks for clipboard, location, notifications, or playback on page load.
Secure contexts and feature checks
TRUST FIRSTIn most browsers, these APIs need a secure context: HTTPS in production or localhost during development. That rule matters because a network attacker should not be able to rewrite a page that asks for your location or clipboard. A framework cannot bypass this; the browser decides.
Support also differs by browser and platform. For browser lessons, that creates a React rule: feature detection happens after mount or after a user action, never during server render. The server does not have navigator, window, permissions, or an audio device, so guessing during render can create hydration mismatches.
Pick a feature, predict which messages print, then step through the permission flow.
script
const state = "prompt"; if (state === "prompt") { console.log("wait for a click before requesting " + feature);} console.log("explain why the app needs " + feature);The replay is a model of a good permission flow, not a browser debugger. It teaches the shape: name the feature, notice the state is still undecided, wait for the person to click, and explain why before the real request.
Clipboard API: copy, paste, and explicit reads
INTERACTIVEThe Clipboard API lives at navigator.clipboard. The safe, common case is writing text after a click: writeText() returns a promise, so show success only after it resolves and show the real error if it rejects. In some browsers clipboard writing needs transient user activation, which means the call should happen directly from the click handler.
The clipboard is not owned by your page. Other apps can use it too, so browsers make your page prove that the person really asked to put something there or take something from it.
- In real life: Put a note on the tray
- In JavaScript:
navigator.clipboard.writeText(text) - In real life: Pick up what someone left
- In JavaScript:
navigator.clipboard.readText() - In real life: The person places an item into your form
- In JavaScript: A
pasteevent withevent.clipboardData
Where the analogy stops: A tray is always visible. The system clipboard is private, so browsers may require a user gesture, a prompt, or a paste menu, especially for reads.
const text = messageInput.value;await navigator.clipboard.writeText(text);status.textContent = "Copied!"; pasteBox.addEventListener("paste", (event) => { status.textContent = event.clipboardData.getData("text");}); const pasted = await navigator.clipboard.readText();(empty)Click Copy, paste into the field, or explicitly try Read clipboard.
navigator.Notice the difference between the paste field and the explicit read button. A normal paste event starts with the person choosing Paste, so the event carries the text for that field. readText() asks the browser to hand your page whatever is currently on the shared tray; in most browsers that is more sensitive and may prompt or show a menu.
Permissions without prompting
DASHBOARDnavigator.permissions.query() checks some permission states without showing a prompt. That makes it useful for dashboards, disabled buttons, and helpful explanations. It does not support every name in every browser, so the code catches TypeError and still uses feature detection.
const names = ["geolocation", "notifications", "camera"]; for (const name of names) { try { const status = await navigator.permissions.query({ name }); render(name, status.state); status.addEventListener("change", () => render(name, status.state)); } catch (error) { render(name, "unsupported"); }}This dashboard calls permissions.query() after mount. It does not request access; it only reports what the browser is willing to reveal.
TypeError for names they do not support.| State | What it means | Good UI response |
|---|---|---|
granted | The browser says the origin can use the capability. | Run the feature, still showing clear controls. |
prompt | The person has not decided yet, or the browser cannot expose the final answer in advance. | Wait until the user clicks the feature, then request. |
denied | The browser or user blocked it. | Do not nag. Explain settings and offer a fallback. |
| Unsupported name | Some browsers throw TypeError for permission names they do not know. | Catch it and check the real feature another way. |
The returned PermissionStatus can fire a change event. That matters when someone changes a browser setting while your page is open. Do not treat prompt as permission; it means “ask later, when the person actually wants this feature.”
Geolocation: ask where we are
INTERACTIVEnavigator.geolocation.getCurrentPosition() accepts three arguments: a success callback, an error callback, and options. The result gives latitude, longitude, and accuracy in meters. The options shape the trade-off: enableHighAccuracy may be slower or use more power, timeout limits waiting, and maximumAge says how old a cached answer may be.
You can ask politely, but you cannot demand a perfect answer. Good apps make refusal and rough answers part of the design.
- In real life: They can refuse to answer
- In JavaScript: Error code 1:
PERMISSION_DENIED - In real life: They can point roughly down the street
- In JavaScript: Coordinates plus an
accuracyradius - In real life: They might take too long
- In JavaScript: Error code 3:
TIMEOUT - In real life: They remember a recent answer
- In JavaScript:
maximumAgeallows cached positions
Where the analogy stops: A browser may combine GPS, Wi-Fi, IP address, and platform policy. It is not literally one passer-by, and the answer may be unavailable.
navigator.geolocation.getCurrentPosition( (position) => { const { latitude, longitude, accuracy } = position.coords; show(formatCoordinates(latitude, longitude, accuracy)); }, (error) => show(mapGeolocationError(error.code)), { enableHighAccuracy: true, timeout: 8000, maximumAge: 60000 },);Click Share my location, or use the simulated success path if you prefer not to share.
Click Share my location, or use the simulated success path if you prefer not to share.
This lesson rounds coordinates to two decimals so examples stay readable. Real apps should be clear about whether they need exact location, approximate region, or a manual city. The lab never stores or sends coordinates, and the simulated path is there for readers who decline.
Notifications: ask only when the feature makes sense
CLICK ONLYThe Notifications API has its own state at Notification.permission: granted, denied, or default. The request method is Notification.requestPermission(), and it should run only after the person chooses a notification feature.
if (Notification.permission === "default") { const permission = await Notification.requestPermission(); if (permission === "granted") { new Notification("Device APIs lab", { body: "Thanks for opting in." }); }}Check another way
Click the button to request permission only if you want to test notifications.
A page can create a test notification while it is open. Real apps often use a service worker for push notifications so messages can arrive when the page is not focused. Some platforms restrict web notifications even more, for example allowing them only for installed web apps. When behavior varies, say “in most browsers” instead of pretending every platform is identical.
Audio and video: one media API
LIVE AUDIOAn <audio> or <video> element is an HTMLMediaElement. It has methods like play() and pause(), properties like currentTime, volume, and playbackRate, plus events such as play, pause, timeupdate, and ended.
You send commands, but the device still reports whether those commands worked. That is why play() gives you a promise and media events keep the interface synced.
- In real life: Press Play
- In JavaScript: Call
audio.play()and await the promise - In real life: The TV refuses to start with sound
- In JavaScript: The promise can reject with
NotAllowedError - In real life: Change volume or speed
- In JavaScript: Set
volumeorplaybackRate - In real life: The TV reports what happened
- In JavaScript: Listen for
play,pause,timeupdate, andended
Where the analogy stops: A remote does not know browser autoplay policy, codecs, or muted playback rules. A media element does, so handle promises and events.
const audio = document.querySelector("audio"); try { await audio.play(); log("play() resolved");} catch (error) { log(error.name);} audio.volume = 0.4;audio.playbackRate = 1.25;audio.addEventListener("timeupdate", updateClock);0.351×0.00s- Audio Blob will be created after mount.
currentTime is 0.00s. Audio Blob will be created after mount.
The lab generates a tiny WAV file in the page with a Blob URL, so it does not fetch external media. Autoplay rules are why the Play button matters: in most browsers, trying to start unmuted media without a user gesture can reject with NotAllowedError. A video element shares the same core API.
Where you’ll use this
SORTERProduct code often starts with a sentence, not an API name: “copy the invite link,” “show nearby classrooms,” “remind me when export is done,” or “slow down the lesson video.” Sort by what the task touches, then ask what permission or user gesture is required.
- Copy an invite link after the user presses Share
- Let a learner paste a coupon into a field
- Find workshops near the reader’s current city
- Show “about 30 m accuracy” beside a map dot
- Remind someone when a long export finishes
- Ask once before sending calendar reminder alerts
- Play a confirmation chime generated in the page
- Let a learner slow a tutorial video to 0.75×
Choose the API family for each realistic task.
- Can the app still work if the person says no?
- Can this start from a click, paste, or media control?
- Does this browser support the API in this secure context?
- What exact error or permission state will the UI show?
Common misconceptions
PITFALLS- “Permission is global.” Permission is per capability and usually per origin. Location does not grant notifications.
- “
promptmeans I can use it.” It means the browser has not received a final answer. Ask only from a relevant action. - “
permissions.querytells me everything.” Names vary by browser; unsupported names can throw. Feature-detect too. - “Geolocation is exact.” Coordinates include an accuracy radius and can be stale, unavailable, or denied.
- “
play()always starts sound.” The promise can reject because of autoplay policy, missing data, or unsupported media. - “Clipboard reads are the same as paste.” A paste event is user-initiated; explicit clipboard reads are more sensitive.
Practice exercises
5 EXERCISESPredict the exact text printed by this geolocation error helper.
function messageFor(code) {
if (code === 1) return "Permission denied";
if (code === 2) return "Position unavailable";
if (code === 3) return "Timeout";
return "Unknown";
}
console.log(messageFor(3));Code 3 means TIMEOUT, so the helper prints Timeout.
Predict the Mumbai coordinate display for the starter code.
function rounded(lat, lon) {
return lat.toFixed(2) + ", " + lon.toFixed(2);
}
console.log(rounded(19.076, 72.8777));19.076 rounds to 19.08 and 72.8777 rounds to 72.88, so the printed text is 19.08, 72.88.
Predict the message for a denied permission state.
function shouldAsk(state) {
return state === "prompt" ? "ask on click" : "do not prompt";
}
console.log(shouldAsk("denied"));The helper returns do not prompt for every state except prompt, because denied should lead to explanation or settings, not another prompt.
Write the missing code for a copy button in your editor or console. Keep the request inside the click handler.
button.addEventListener("click", async () => {
// copy inviteLink here
});button.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(inviteLink);
status.textContent = "Invite link copied.";
} catch (error) {
status.textContent = error instanceof Error ? error.message : "Copy failed.";
}
});The API call stays inside the user gesture, and the UI handles both success and rejection.
Fix this media starter so a blocked play request becomes useful UI.
async function startAudio(audio, status) {
await audio.play();
status.textContent = "Playing";
}async function startAudio(audio, status) {
try {
await audio.play();
status.textContent = "Playing";
} catch (error) {
status.textContent = error instanceof Error ? error.name : "Playback blocked";
}
}A rejected play() promise is expected browser behavior, so the UI reports it instead of leaving the button broken.
Check your understanding
7 QUESTIONSQuestion 1 of 7Which statement best describes secure contexts for these APIs?
Choose an answer to see the explanation.
Question 2 of 7What does the coordinate formatter output for Mumbai?
Read the code, then predictfunction formatCoordinates(latitude, longitude, accuracy) { return latitude.toFixed(2) + ", " + longitude.toFixed(2) + " (about " + Math.round(accuracy) + " m accuracy)"; } console.log(formatCoordinates(19.076, 72.8777, 28.6));Choose an answer to see the explanation.
Question 3 of 7Which geolocation error code means timeout?
Choose an answer to see the explanation.
Question 4 of 7What should happen before calling
Notification.requestPermission()?Choose an answer to see the explanation.
Question 5 of 7What does
play()give you on a media element?Choose an answer to see the explanation.
Question 6 of 7What does this permission helper print?
Read the code, then predictfunction permissionBadge(state) { return state === "granted" ? "Allowed" : state === "denied" ? "Blocked" : state === "prompt" ? "Ask on use" : "Check another way"; } console.log(permissionBadge("unsupported"));Choose an answer to see the explanation.
Question 7 of 7Which API is the best fit for a Paste button that reads text only after the click?
Choose an answer to see the explanation.
Key takeaways
- Powerful browser APIs usually require a secure context and real browser feature detection.
- Permission prompts belong behind user intent, not page load.
- Clipboard reads, geolocation, notifications, and media playback all have normal failure paths.
- Geolocation includes accuracy and error codes; never assume exact coordinates.
HTMLMediaElementpowers both audio and video with promises, properties, and events.
Final definition.
Device and platform APIs are browser-controlled bridges to user-sensitive capabilities, so good JavaScript checks support, asks at the right moment, and handles refusal with care.
Up next: Web Workers.