diff --git a/docs/src/guide/options.md b/docs/src/guide/options.md index dd17145019..8615491f8b 100644 --- a/docs/src/guide/options.md +++ b/docs/src/guide/options.md @@ -39,7 +39,8 @@ These options can be used to define the initial checks to display the [`BoosterL ````js { performance: true, - browserSupport: true + browserSupport: true, + battery: true } ```` @@ -47,6 +48,7 @@ These options can be used to define the initial checks to display the [`BoosterL | ---------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `performance` | `Boolean` | yes | Checking whether the [minimum characteristic values](/guide/options#performancemetrics) have been reached. If the test is negative, the [`BoosterLayer`](/components/booster-layer) will be displayed. | `true` | | `browserSupport` | `Boolean` | yes | Check if the current browser on client side is supported. If the test is negative, the [`BoosterLayer`](/components/booster-layer) will be displayed. | `true` | + | `battery` | `Boolean` | yes | Check if the current user save power in browser. If the test is negative, the [`BoosterLayer`](/components/booster-layer) will be displayed. | `true` | ::: info For the browser support detection, the default [Browserslist](https://github.com/browserslist/browserslist) of the NuxtJS configuration is used. diff --git a/playground/components/InfoLayer.vue b/playground/components/InfoLayer.vue index 38bd812f57..54a998c39d 100644 --- a/playground/components/InfoLayer.vue +++ b/playground/components/InfoLayer.vue @@ -9,6 +9,7 @@
  • outdated browser
  • reduced-bandwidth
  • weak hardware
  • +
  • low battery
  • mediaContent, + filename: MODULE_NAME + '/blobs.mjs', + write: true + }); } async function addModules(nuxt, moduleOptions) { diff --git a/src/runtime/components/BoosterLayer.vue b/src/runtime/components/BoosterLayer.vue index 083f07f811..38e04d5db5 100644 --- a/src/runtime/components/BoosterLayer.vue +++ b/src/runtime/components/BoosterLayer.vue @@ -17,6 +17,8 @@
  • slow connection
  • weak hardware
  • + +
  • low battery
  • @@ -96,5 +98,9 @@ useHead({ display: none; } +#nuxt-speedkit-message-low-battery { + display: none; +} + /*! purgecss end ignore */ diff --git a/src/runtime/utils/entry.js b/src/runtime/utils/entry.js index 26184fedc0..fcac110d2f 100644 --- a/src/runtime/utils/entry.js +++ b/src/runtime/utils/entry.js @@ -55,3 +55,44 @@ export function initReducedView() { tmp.remove(); }); } + +export async function hasBatteryPerformanceIssue(videoBlob) { + try { + if (await isBatteryLow()) { + throw new Error('Battery is low.'); + } + } catch (error) { + if (error.message === 'Battery is low.') { + throw error; + } + await canVideoPlay(videoBlob); + } +} + +/** + * Checks if battery still has enough energy. + * This check is for Chrome and all other browsers that support this setting. + * + * Condition is: The device is not charging and Battery is below <= 20%. + * @see https://blog.google/products/chrome/new-chrome-features-to-save-battery-and-make-browsing-smoother/ + * @see https://developer.chrome.com/blog/memory-and-energy-saver-mode/ + **/ +async function isBatteryLow() { + const MIN_BATTERY_LEVEL = 0.2; + const battery = await window.navigator.getBattery(); + return !battery.charging && battery.level <= MIN_BATTERY_LEVEL; +} + +/** + * Checking whether a video can be played. + * This check is for IOS and checks if the power saving mode is enabled. + * + * In this case no video will be played automatically and play throws an error. + */ +export function canVideoPlay(blob) { + const video = document.createElement('video'); + video.muted = true; + video.playsinline = true; + video.src = URL.createObjectURL(blob); + return video.play(); +} diff --git a/src/tmpl/entry.tmpl.js b/src/tmpl/entry.tmpl.js index b346c789e3..77982356d3 100644 --- a/src/tmpl/entry.tmpl.js +++ b/src/tmpl/entry.tmpl.js @@ -2,9 +2,10 @@ export default options => { let code = `import { ${ options.performanceCheck ? `run, ` : `` }hasSufficientPerformance, setup } from '#booster/utils/performance'; -import { triggerRunCallback, observeBoosterButton, setupBoosterLayer, updateBoosterLayerMessage, initReducedView } from '#booster/utils/entry'; +import { triggerRunCallback, observeBoosterButton, setupBoosterLayer, updateBoosterLayerMessage, initReducedView, hasBatteryPerformanceIssue } from '#booster/utils/entry'; import Deferred from '#booster/classes/Deferred'; import { isSupportedBrowser } from '#booster/utils/browser'; +import {video as videoBlob} from './blobs.mjs'; `; @@ -46,15 +47,38 @@ function client () { const forceInit = ('__NUXT_BOOSTER_FORCE_INIT__' in window && window.__NUXT_BOOSTER_FORCE_INIT__); async function initApp(force) { + if (initialized) { deferred.resolve(); } document.documentElement.classList.remove('nuxt-booster-reduced-view'); + `; + + if (!options.ignore.battery) { + code += ` try { + if (!force) { + await hasBatteryPerformanceIssue(videoBlob) + } + } catch (error) { + + console.warn(error) + + triggerRunCallback(false); + + if (!!layerEl) { + // User must interact via the layer. + updateSpeedkitLayerMessage(layerEl, 'nuxt-speedkit-message-low-battery'); + return null; + } + } + `; + } - `; + code += ` + try {`; if (options.performanceCheck) { code += ` @@ -75,6 +99,9 @@ if (!force) { deferred.resolve(); } catch (error) { + + console.warn(error) + triggerRunCallback(false); if (!!layerEl) { diff --git a/src/utils/blob.js b/src/utils/blob.js new file mode 100644 index 0000000000..7c0663f006 --- /dev/null +++ b/src/utils/blob.js @@ -0,0 +1,22 @@ +import { promises as fsPromises } from 'fs'; +import mime from 'mime-types'; + +async function getFileInfo(name, file) { + return { + name, + // eslint-disable-next-line security/detect-non-literal-fs-filename + file: await fsPromises.readFile(file), + mimeType: mime.lookup(file) + }; +} + +export async function getTemplate(files) { + return (await Promise.all(files.map(file => getFileInfo(...file)))) + .map( + ({ name, file, mimeType }) => + `export const ${name} = new Blob([new Uint8Array([${[...file].join( + ', ' + )}])], {type: '${mimeType}'});` + ) + .join('\n'); +} diff --git a/src/utils/options.js b/src/utils/options.js index e9ab1b6bc7..736a07867d 100644 --- a/src/utils/options.js +++ b/src/utils/options.js @@ -13,7 +13,8 @@ export function getDefaultOptions() { detection: { performance: true, - browserSupport: true + browserSupport: true, + battery: true }, performanceMetrics: {