Feature Detection

Cloud Phone Feature Detection

The Feature Detection API allows you to detect specific Cloud Phone client capabilities so your app can offer the best user experience. Use feature detection to build cross-browser web apps that conditionally enable certain features based on supported software and hardware.

Example

PodLP uses feature detection to show a “Download Episode” menu item only on devices that support file downloads using the download property on an HTMLAnchorElement.

const fileDownloadSupported = await navigator.hasFeature("FileDownload");

if (fileDownloadSupported) {
  downloadLink.setAttribute("download", "podcast.mp3");
  downloadLink.classList.remove("hidden");
}

Features

The following features are valid names that can be passed to the navigator.hasFeature function. Invalid names resolve to false.

Feature names passed to navigator.hasFeature are case sensitive!

AudioCapture

True when audio recording using the getUserMedia is supported.

This is not the same as detecting hardware capabilities or permission status. Catch exceptions throw by getUserMedia to gracefully handle issues accessing the microphone.

if (await navigator.hasFeature("AudioCapture")) {
  return navigator.mediaDevices
    .getUserMedia({ audio: true })
    .then((mediaStream) => {
      const audio = document.querySelector("audio");
      audio.srcObject = mediaStream;
    })
    .catch((err) => {
      // Throws NotAllowedError if permission denied
      // Throws NotFoundError if no microphone
    });
}

AudioPlay

True when audio playback is supported for HTMLAudioElement.

AudioSeek

True when audio seeking is supported for HTMLMediaElement.currentTime.

// Show a scrubber when seeking is supported
if (await navigator.hasFeature("AudioSeek")) {
  scrubber.classList.remove("hidden");
}

AudioUpload

True when audio uploads are supported using the <input type="file"> element. This specifically only includes the wildcard MIME type audio/* as the accept attribute value.

<form id="audioForm" class="hidden">
  <label for="audioFile">Upload Audio:</label>
  <input type="file" id="audioFile" name="audioFile" accept="audio/*" />
</form>
// Show audio upload button when FileUpload is supported
if (await navigator.hasFeature("FileUpload")) {
  document.getElementById("audioForm").classList.remove("hidden");
}

VideoCapture

Same as AudioCapture but for video. True when video recording using the getUserMedia is supported.

VideoSeek

Same as AudioSeek but for video. True when audio seeking is supported using HTMLMediaElement.currentTime.

VideoUpload

Same as AudioUpload but for video. True when video uploads are supported using the <input type="file"> element. This specifically only includes the wildcard MIME type video/* as the accept attribute value.

EmbeddedTextInput

True when the Cloud Phone client supports embedded text input where the UI displays the Input Method Editor (IME) using a header and footer while maintaining visibility of the <input> element (right screenshot).

Full screen text input screenshot
Full screen text input
Embedded text input screenshot
Embedded text input

x-puffin-entersfullscreen

Devices that don’t support embedded input display a fullscreen dialog (left screenshot). This behavior can be fully disabled using the custom x-puffin-entersfullscreen attribute. Apps disabling the fullscreen IME must intercept KeyEvents and manually change the input value.

Telegram client screenshot

Although Cloud Phone can render emojis, the native IME does not support emoji text input. Create a custom emoji keyboard by mapping keys 0-9 to emojis displayed on screen when the input is focused.

<input type="text" x-puffin-entersfullscreen="off" id="customTextInput" />
// Map emojis to 0-9 keys
const emojiMap = new Map([
  ["1", "๐Ÿ˜€"],
  ["2", "๐Ÿ˜ƒ"],
  ["3", "๐Ÿ˜ƒ"]
  // ...
]);

const customInput = document.getElementById("customTextInput");

// Show and hide emoji keyboard
customInput.addEventListener("focus", () => {
  showEmojiKeyboard = true;
});
customInput.addEventListener("blur", () => {
  showEmojiKeyboard = false;
});

// Append emoji when key is pressed
customInput.addEventListener("keydown", (e) => {
  customInput.value += customInput.get(e.key) || "";
});

FileDownload

Cloud Phone File Download

True when downloads are supported using the download property on an HTMLAnchorElement. Download links open a confirmation dialog displaying the file name and size. File name is derived from either the filename parameter of the Content-Disposition header, or value of the download attribute.

<a href="./podcast.mp3" download id="podcastDownloadLink" class="hidden" />
// Show download link when FileDownload is supported
if (await navigator.hasFeature("FileDownload")) {
  document.getElementById("podcastDownloadLink").classList.remove("hidden");
}

Without the Content-Disposition header, both Cloud Phone and Google Chrome block cross-origin <a download>

FileUpload

Cloud Phone File Upload

True when file uploads are supported using the <input type="file"> element. This includes any filename extension or MIME type in the accept attribute.

Clicking on an <input type="file"> element opens a native fullscreen file picker, like the one pictured above. Users can choose between internal storage (“Phone”) and external storage (“MemoryCard”).

<form id="documentForm" class="hidden">
  <label for="document">Upload PDF:</label>
  <input type="file" id="document" name="document" accept=".pdf" />
</form>
// Show upload button when FileUpload is supported
if (await navigator.hasFeature("FileUpload")) {
  document.getElementById("documentForm").classList.remove("hidden");
}

ImageUpload

Same as AudioUpload but for images. True when image uploads are supported using the <input type="file"> element. This specifically only includes the wildcard MIME type image/* as the accept attribute value.

SmsScheme

True when links using the sms: URI scheme are supported. The format for URLs of this type is sms:<phone>, where <phone> is an optional parameter that specifies a phone number to compose a new SMS message. Valid values can contain the digits 0 through 9, plus (+), hyphen (-), and period (.) characters.

<a href="sms:1-408-555-1212">New SMS Message</a>

TelScheme

True when links using the tel: URI scheme are supported.

<a href="tel:1-408-555-1212">New SMS Message</a>

Vibrate

True if pulses from vibration hardware are supported using navigator.vibrate.

navigator.vibrate always returns true unless invalid parameters are provided. Use navigator.hasFeature('Vibrate') to detect if the Vibration API is supported.

Interface

The following TypeScript defines the global hasFeature function on the navigator object on both the main thread and a Web Worker context.

declare global {
  interface Navigator {
    hasFeature(name: string): Promise<boolean>;
  }

  interface WorkerNavigator {
    hasFeature(name: string): Promise<boolean>;
  }
}

Availability

The Feature Detection API is available on all Cloud Phone client versions. navigator.hasFeature only resolves to true when a given feature is available.

Summary

The Feature Detection API lets you conditionally enable app features based on the capabilities supported by the Cloud Phone client. Use feature detection to build the best user experience without crashing or silently failing.