From 643d41757347010bbee75f1cd9da3c27ebc1b43c Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Mon, 11 Sep 2023 17:52:14 +0200 Subject: [PATCH 1/8] fix(entry): added battery level check - occurs when battery is not charging and <= 20%. - added message in speedkitLayer. --- playground/components/InfoLayer.vue | 1 + src/module.mjs | 1 + src/runtime/components/SpeedkitLayer.vue | 7 +++++ src/runtime/tmpl/entry.mjs | 40 +++++++++++++++--------- src/runtime/utils/entry.mjs | 14 +++++++++ src/utils/options.mjs | 3 +- 6 files changed, 51 insertions(+), 15 deletions(-) diff --git a/playground/components/InfoLayer.vue b/playground/components/InfoLayer.vue index 1ffe3323d9..5e9a4b7701 100644 --- a/playground/components/InfoLayer.vue +++ b/playground/components/InfoLayer.vue @@ -9,6 +9,7 @@
  • outdated browser
  • reduced-bandwidth
  • weak hardware
  • +
  • low battery
  • weak hardware
  • + + +
  • low battery
  • @@ -98,5 +101,9 @@ useHead({ display: none; } +#nuxt-speedkit-message-low-battery { + display: none; +} + /*! purgecss end ignore */ diff --git a/src/runtime/tmpl/entry.mjs b/src/runtime/tmpl/entry.mjs index 04e19bf0c0..4e14b2733e 100644 --- a/src/runtime/tmpl/entry.mjs +++ b/src/runtime/tmpl/entry.mjs @@ -35,33 +35,45 @@ function client () { const forceInit = ('__NUXT_SPEEDKIT_FORCE_INIT__' in window && window.__NUXT_SPEEDKIT_FORCE_INIT__); async function initApp(force) { + if (initialized) { deferred.resolve(); } document.documentElement.classList.remove('nuxt-speedkit-reduced-view'); - try { + if (<%= !options.ignoreBattery %> && await hasBatteryPerformanceIssue()) { + triggerRunCallback(false); - <% if (options.performanceCheck) { %>if (!force) { - await run(<%= options.runOptions ? JSON.stringify(options.runOptions) : '' %>); - }<% } %> + if (!!layerEl) { + // User must interact via the layer. + updateSpeedkitLayerMessage(layerEl, 'nuxt-speedkit-message-low-battery'); + return null; + } + } else { - initialized = true; + try { + <% if (options.performanceCheck) { %>if (!force) { + await run(<%= options.runOptions ? JSON.stringify(options.runOptions) : '' %>); + }<% } %> - triggerRunCallback(true); + initialized = true; - deferred.resolve(); + triggerRunCallback(true); - } catch (error) { - triggerRunCallback(false); + deferred.resolve(); - if (!!layerEl) { - // User must interact via the layer. - updateSpeedkitLayerMessage(layerEl, 'nuxt-speedkit-message-weak-hardware'); - return null; + } catch (error) { + triggerRunCallback(false); + + if (!!layerEl) { + // User must interact via the layer. + updateSpeedkitLayerMessage(layerEl, 'nuxt-speedkit-message-weak-hardware'); + return null; + } } - } + + }; return null; }; diff --git a/src/runtime/utils/entry.mjs b/src/runtime/utils/entry.mjs index 054d0043e4..61937c8522 100644 --- a/src/runtime/utils/entry.mjs +++ b/src/runtime/utils/entry.mjs @@ -56,3 +56,17 @@ export function initReducedView() { tmp.remove(); }); } + +const MIN_BATTERY_LEVEL = 0.2; +// https://blog.google/products/chrome/new-chrome-features-to-save-battery-and-make-browsing-smoother/ +export async function hasBatteryPerformanceIssue() { + try { + const battery = await window.navigator.getBattery(); + if (!battery.charging && battery.level <= MIN_BATTERY_LEVEL) { + return true; + } + } catch (error) { + // Ignore Check + } + return false; +} diff --git a/src/utils/options.mjs b/src/utils/options.mjs index 7cd93cab96..908ee916fb 100644 --- a/src/utils/options.mjs +++ b/src/utils/options.mjs @@ -12,7 +12,8 @@ export function getDefaultOptions() { detection: { performance: true, - browserSupport: true + browserSupport: true, + battery: true }, performanceMetrics: { From 2b9206cc1f999ef474041fc237c7e303e633ed5b Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Wed, 13 Sep 2023 12:12:35 +0200 Subject: [PATCH 2/8] fix(entry): added video check (ios) --- src/media/video.mp4 | Bin 0 -> 1493 bytes src/module.mjs | 9 +++++++ src/runtime/tmpl/entry.mjs | 43 +++++++++++++++++-------------- src/runtime/utils/entry.mjs | 50 ++++++++++++++++++++++++++++++++---- src/utils/blob.mjs | 22 ++++++++++++++++ 5 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 src/media/video.mp4 create mode 100644 src/utils/blob.mjs diff --git a/src/media/video.mp4 b/src/media/video.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..1431e98dd3f5bb28c282d3d732a3a1fc13cf3d63 GIT binary patch literal 1493 zcmeHHO-sW-5Z$D$cn}OyK`=rq^=7}c+G`IUJa`ik#7o*uZD_U$*=<2j`7uHf{1GDl z1OJ2v!Gm5r>6;{NZ56!et&eVJ_RZ{@-Py2=F|(~MeMfqNF`bdc*b#oM${1U02eu9V zMQ}^yjZE%({o@Y&97zZ2!~blBKX3W>_ST2tck=HTAD{cZ@Vo&eT`}nJY3bAx=4GZa z`b{u30X;KsOfrA+Qdsb;0;`MQx#~tW!HuCOUU|%5K~!R<&M$QfHdhiFrtM0VXpus! z8w+y59SdAe@Y%E99^ge_ClAreVu!myqQRus?~Q!2$C&d&tH)iNa0=u^4bYPYW!*CPt zTN*aoSS3n(!+3qqfYDcs1sIL`ye3uDqbLP`;L_xqDBmInpmH=QpDe|$U`GJ2p{zzd z&Pvttk^tpyV>J6*S+Hlqjx2@qU;BwW2rS>HV~ mediaContent, + filename: MODULE_NAME + '/blobs.mjs', + write: true + }); } async function addModules(nuxt, moduleOptions) { diff --git a/src/runtime/tmpl/entry.mjs b/src/runtime/tmpl/entry.mjs index 4e14b2733e..b53e9ffc6a 100644 --- a/src/runtime/tmpl/entry.mjs +++ b/src/runtime/tmpl/entry.mjs @@ -1,7 +1,8 @@ import { <% if (options.performanceCheck) { %>run, <% } %>hasSufficientPerformance, setup } from '#speedkit/utils/performance'; -import { triggerRunCallback, observeSpeedkitButton, setupSpeedkitLayer, updateSpeedkitLayerMessage, initReducedView } from '#speedkit/utils/entry'; +import { triggerRunCallback, observeSpeedkitButton, setupSpeedkitLayer, updateSpeedkitLayerMessage, initReducedView, hasBatteryPerformanceIssue } from '#speedkit/utils/entry'; import Deferred from '#speedkit/classes/Deferred.mjs'; import { isSupportedBrowser } from '#speedkit/utils/browser'; +import {video as videoBlob} from './blobs.mjs'; <% if (options.webpack) { %> // webpack @@ -42,7 +43,12 @@ function client () { document.documentElement.classList.remove('nuxt-speedkit-reduced-view'); - if (<%= !options.ignoreBattery %> && await hasBatteryPerformanceIssue()) { + <% if (!options.ignoreBattery) { %> + try { + if (!force) { + await hasBatteryPerformanceIssue(videoBlob) + } + } catch (error) { triggerRunCallback(false); if (!!layerEl) { @@ -50,30 +56,29 @@ function client () { updateSpeedkitLayerMessage(layerEl, 'nuxt-speedkit-message-low-battery'); return null; } - } else { + } + <% } %> - try { - <% if (options.performanceCheck) { %>if (!force) { - await run(<%= options.runOptions ? JSON.stringify(options.runOptions) : '' %>); - }<% } %> + try { + <% if (options.performanceCheck) { %>if (!force) { + await run(<%= options.runOptions ? JSON.stringify(options.runOptions) : '' %>); + }<% } %> - initialized = true; + initialized = true; - triggerRunCallback(true); + triggerRunCallback(true); - deferred.resolve(); + deferred.resolve(); - } catch (error) { - triggerRunCallback(false); + } catch (error) { + triggerRunCallback(false); - if (!!layerEl) { - // User must interact via the layer. - updateSpeedkitLayerMessage(layerEl, 'nuxt-speedkit-message-weak-hardware'); - return null; - } + if (!!layerEl) { + // User must interact via the layer. + updateSpeedkitLayerMessage(layerEl, 'nuxt-speedkit-message-weak-hardware'); + return null; } - - }; + } return null; }; diff --git a/src/runtime/utils/entry.mjs b/src/runtime/utils/entry.mjs index 61937c8522..97b6046757 100644 --- a/src/runtime/utils/entry.mjs +++ b/src/runtime/utils/entry.mjs @@ -57,16 +57,56 @@ export function initReducedView() { }); } -const MIN_BATTERY_LEVEL = 0.2; -// https://blog.google/products/chrome/new-chrome-features-to-save-battery-and-make-browsing-smoother/ -export async function hasBatteryPerformanceIssue() { +export async function hasBatteryPerformanceIssue(videoBlob) { + await isBatteryLow(); + 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; + try { const battery = await window.navigator.getBattery(); if (!battery.charging && battery.level <= MIN_BATTERY_LEVEL) { - return true; + throw new Error('Battery is low.'); } } catch (error) { // Ignore Check } - return false; +} + +/** + * 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. + */ +export async function canVideoPlay(blob) { + const VIDEO_TIMEOUT = 250; + + const video = document.createElement('video'); + video.muted = true; + video.setAttribute('muted', 'muted'); + video.setAttribute('playsinline', 'playsinline'); + video.src = URL.createObjectURL(blob); + + const timeout = setTimeout(() => { + throw new Error('Video playback not possible. Reason: timeout'); + }, VIDEO_TIMEOUT); + + try { + await video.play(); + clearTimeout(timeout); + } catch (error) { + clearTimeout(timeout); + throw new Error("Video playback not possible. Reason: can't play video"); + } } diff --git a/src/utils/blob.mjs b/src/utils/blob.mjs new file mode 100644 index 0000000000..7c0663f006 --- /dev/null +++ b/src/utils/blob.mjs @@ -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'); +} From 54ceb684812d31f777ec67e8bbb5ca841854d833 Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Wed, 13 Sep 2023 13:50:00 +0200 Subject: [PATCH 3/8] fix(entry): added ignore object --- src/module.mjs | 6 ++++-- src/runtime/tmpl/entry.mjs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/module.mjs b/src/module.mjs index f5b6c3086b..b08d1a0710 100644 --- a/src/module.mjs +++ b/src/module.mjs @@ -144,8 +144,10 @@ async function addBuildTemplates(nuxt, options) { entry: join(nuxt.options.appDir, 'entry'), runOptions: options.runOptions, ssr: nuxt.options.ssr, - ignorePerformance: !options.detection.performance, - ignoreBattery: !options.detection.battery, + ignore: { + battery: !options.detection.battery, + performance: !options.detection.performance + }, performanceMetrics: JSON.stringify(options.performanceMetrics || {}), supportedBrowserDetector } diff --git a/src/runtime/tmpl/entry.mjs b/src/runtime/tmpl/entry.mjs index b53e9ffc6a..ec124dce7c 100644 --- a/src/runtime/tmpl/entry.mjs +++ b/src/runtime/tmpl/entry.mjs @@ -43,7 +43,7 @@ function client () { document.documentElement.classList.remove('nuxt-speedkit-reduced-view'); - <% if (!options.ignoreBattery) { %> + <% if (!options.ignore.battery) { %> try { if (!force) { await hasBatteryPerformanceIssue(videoBlob) @@ -95,7 +95,7 @@ function client () { setup(<%= options.performanceMetrics %>); - if(('__NUXT_SPEEDKIT_AUTO_INIT__' in window && window.__NUXT_SPEEDKIT_AUTO_INIT__) || ((<%= !options.ignorePerformance %> && hasSufficientPerformance()) && supportedBrowser)) { + if(('__NUXT_SPEEDKIT_AUTO_INIT__' in window && window.__NUXT_SPEEDKIT_AUTO_INIT__) || ((<%= !options.ignore.performance %> && hasSufficientPerformance()) && supportedBrowser)) { initApp(); } else { setupSpeedkitLayer(layerEl, supportedBrowser) From a3b948765f9522c730ca91fde8fe2730d3d7d149 Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Thu, 21 Sep 2023 10:17:05 +0200 Subject: [PATCH 4/8] fix(entry): remove slient catches - removed video timeout, only catch by play reject - added warn logs for catches - removed setAttributes, only set property --- src/runtime/tmpl/entry.mjs | 6 ++++++ src/runtime/utils/entry.mjs | 22 ++++------------------ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/runtime/tmpl/entry.mjs b/src/runtime/tmpl/entry.mjs index ec124dce7c..69c7252d44 100644 --- a/src/runtime/tmpl/entry.mjs +++ b/src/runtime/tmpl/entry.mjs @@ -49,6 +49,9 @@ function client () { await hasBatteryPerformanceIssue(videoBlob) } } catch (error) { + + console.warn(error) + triggerRunCallback(false); if (!!layerEl) { @@ -71,6 +74,9 @@ function client () { deferred.resolve(); } catch (error) { + + console.warn(error) + triggerRunCallback(false); if (!!layerEl) { diff --git a/src/runtime/utils/entry.mjs b/src/runtime/utils/entry.mjs index 97b6046757..9ffb07365b 100644 --- a/src/runtime/utils/entry.mjs +++ b/src/runtime/utils/entry.mjs @@ -72,14 +72,9 @@ export async function hasBatteryPerformanceIssue(videoBlob) { **/ async function isBatteryLow() { const MIN_BATTERY_LEVEL = 0.2; - - try { - const battery = await window.navigator.getBattery(); - if (!battery.charging && battery.level <= MIN_BATTERY_LEVEL) { - throw new Error('Battery is low.'); - } - } catch (error) { - // Ignore Check + const battery = await window.navigator.getBattery(); + if (!battery.charging && battery.level <= MIN_BATTERY_LEVEL) { + throw new Error('Battery is low.'); } } @@ -90,23 +85,14 @@ async function isBatteryLow() { * In this case no video will be played automatically. */ export async function canVideoPlay(blob) { - const VIDEO_TIMEOUT = 250; - const video = document.createElement('video'); video.muted = true; - video.setAttribute('muted', 'muted'); - video.setAttribute('playsinline', 'playsinline'); + video.playsinline = true; video.src = URL.createObjectURL(blob); - const timeout = setTimeout(() => { - throw new Error('Video playback not possible. Reason: timeout'); - }, VIDEO_TIMEOUT); - try { await video.play(); - clearTimeout(timeout); } catch (error) { - clearTimeout(timeout); throw new Error("Video playback not possible. Reason: can't play video"); } } From 2ac023e6fa5e206aea299ea6bd0b72afa7b42efe Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Tue, 10 Oct 2023 15:38:19 +0200 Subject: [PATCH 5/8] fix(entry): remove try catch --- src/runtime/utils/entry.mjs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/runtime/utils/entry.mjs b/src/runtime/utils/entry.mjs index 9ffb07365b..7452e2e7ee 100644 --- a/src/runtime/utils/entry.mjs +++ b/src/runtime/utils/entry.mjs @@ -84,15 +84,10 @@ async function isBatteryLow() { * * In this case no video will be played automatically. */ -export async function canVideoPlay(blob) { +export function canVideoPlay(blob) { const video = document.createElement('video'); video.muted = true; video.playsinline = true; video.src = URL.createObjectURL(blob); - - try { - await video.play(); - } catch (error) { - throw new Error("Video playback not possible. Reason: can't play video"); - } + return video.play(); } From 9054d5b58ac39a1011b837c85fd125a69c7957e0 Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Mon, 13 Nov 2023 20:08:08 +0100 Subject: [PATCH 6/8] fix(entry): added catch for video play - updated docs --- docs/src/guide/options.md | 12 +++++++----- src/runtime/utils/entry.mjs | 16 ++++++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/src/guide/options.md b/docs/src/guide/options.md index 5ca978599f..63a834aca2 100644 --- a/docs/src/guide/options.md +++ b/docs/src/guide/options.md @@ -39,14 +39,16 @@ These options can be used to define the initial checks to display the [`Speedkit ````js { performance: true, - browserSupport: true + browserSupport: true, + battery: true } ```` - | Key | Type | Required | Description | Default | - | ---------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | + | Key | Type | Required | Description | Default | + | ---------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `performance` | `Boolean` | yes | Checking whether the [minimum characteristic values](/guide/options#performancemetrics) have been reached. If the test is negative, the [`SpeedkitLayer`](/components/speedkit-layer) will be displayed. | `true` | - | `browserSupport` | `Boolean` | yes | Check if the current browser on client side is supported. If the test is negative, the [`SpeedkitLayer`](/components/speedkit-layer) will be displayed. | `true` | + | `browserSupport` | `Boolean` | yes | Check if the current browser on client side is supported. If the test is negative, the [`SpeedkitLayer`](/components/speedkit-layer) will be displayed. | `true` | + | `battery` | `Boolean` | yes | Check if the current user save power in browser. If the test is negative, the [`SpeedkitLayer`](/components/speedkit-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. @@ -251,7 +253,7 @@ Global option for the [`IntersectionObserver`](https://developer.mozilla.org/en- | Key | Type | Required | Description | Default | | ----------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | - | `component` | `String` | yes | [`rootMargin`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin) value for [`SpeedkitHydrate`](/guide/usage#import-components). | `0%` | + | `component` | `String` | yes | [`rootMargin`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin) value for [`SpeedkitHydrate`](/guide/usage#import-components). | `0%` | | `asset` | `String` | yes | [`rootMargin`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin) value for all static ressources (`v-font`, `SpeedkitPicture` & `SpeedkitImage`). | `0%` | ## `disableNuxtCritters` diff --git a/src/runtime/utils/entry.mjs b/src/runtime/utils/entry.mjs index 7452e2e7ee..986826913e 100644 --- a/src/runtime/utils/entry.mjs +++ b/src/runtime/utils/entry.mjs @@ -58,8 +58,14 @@ export function initReducedView() { } export async function hasBatteryPerformanceIssue(videoBlob) { - await isBatteryLow(); - await canVideoPlay(videoBlob); + if ('BatteryManager' in window && !(await isBatteryLow())) { + throw new Error('Battery is low.'); + } + try { + await canVideoPlay(videoBlob); + } catch (error) { + throw new Error('Video cannot be played.'); + } } /** @@ -73,16 +79,14 @@ export async function hasBatteryPerformanceIssue(videoBlob) { async function isBatteryLow() { const MIN_BATTERY_LEVEL = 0.2; const battery = await window.navigator.getBattery(); - if (!battery.charging && battery.level <= MIN_BATTERY_LEVEL) { - throw new Error('Battery is low.'); - } + 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. + * In this case no video will be played automatically and play throws an error. */ export function canVideoPlay(blob) { const video = document.createElement('video'); From 90a2f66b9e5c5d69615d3f2ecabe418d923bee91 Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Tue, 14 Nov 2023 18:09:01 +0100 Subject: [PATCH 7/8] fix(entry): `hasBatteryPerformanceIssue` modified --- src/runtime/utils/entry.mjs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/runtime/utils/entry.mjs b/src/runtime/utils/entry.mjs index 986826913e..489903745e 100644 --- a/src/runtime/utils/entry.mjs +++ b/src/runtime/utils/entry.mjs @@ -58,13 +58,15 @@ export function initReducedView() { } export async function hasBatteryPerformanceIssue(videoBlob) { - if ('BatteryManager' in window && !(await isBatteryLow())) { - throw new Error('Battery is low.'); - } try { - await canVideoPlay(videoBlob); + if (await isBatteryLow()) { + throw new Error('Battery is low.'); + } } catch (error) { - throw new Error('Video cannot be played.'); + if (error.message === 'Battery is low.') { + throw error; + } + await canVideoPlay(videoBlob); } } @@ -77,9 +79,13 @@ export async function hasBatteryPerformanceIssue(videoBlob) { * @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; + if ('BatteryManager' in window && window.navigator.getBattery) { + const MIN_BATTERY_LEVEL = 0.2; + const battery = await window.navigator.getBattery(); + return !battery.charging && battery.level <= MIN_BATTERY_LEVEL; + } else { + throw new Error('BatteryManager not supported.'); + } } /** From 565125b7cec1865b39cc7214054eabdf419f1319 Mon Sep 17 00:00:00 2001 From: Thorn Walli Date: Tue, 14 Nov 2023 20:20:53 +0100 Subject: [PATCH 8/8] fix(entry): reduce `isBatteryLow` --- src/runtime/utils/entry.mjs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/runtime/utils/entry.mjs b/src/runtime/utils/entry.mjs index 489903745e..3968cc888e 100644 --- a/src/runtime/utils/entry.mjs +++ b/src/runtime/utils/entry.mjs @@ -79,13 +79,9 @@ export async function hasBatteryPerformanceIssue(videoBlob) { * @see https://developer.chrome.com/blog/memory-and-energy-saver-mode/ **/ async function isBatteryLow() { - if ('BatteryManager' in window && window.navigator.getBattery) { - const MIN_BATTERY_LEVEL = 0.2; - const battery = await window.navigator.getBattery(); - return !battery.charging && battery.level <= MIN_BATTERY_LEVEL; - } else { - throw new Error('BatteryManager not supported.'); - } + const MIN_BATTERY_LEVEL = 0.2; + const battery = await window.navigator.getBattery(); + return !battery.charging && battery.level <= MIN_BATTERY_LEVEL; } /**