Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | 6x 14x 2x 86x 86x 13x 2x 1x 1x 11x 7x 1x 6x 1x 9x 73x 7x 66x 17x 9x 8x 49x 4x 45x 132x 2x 1x 1x 1x 1x 1x 1x 59x 39x 20x 13x 4x 9x 2x 1x 1x 2x 1x 1x 1x | /**
* @prettier
*/
/*
* Part of Pleiar.no - a collection of tools for nurses
*
* Copyright (C) Eskild Hustvedt 2017-2018
* Copyright (C) Fagforbundet 2019
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// @flow
import device from "./helper.device";
export type appInstallModes =
| "chrome"
| "firefox-android"
| "safari-ios"
| "samsung"
| "ios-other"
| "facebook-android"
| "facebook-ios"
| "opera"
| "android-webview"
| null;
type appInstallMembers = {|
listeners: Array<() => void>,
mobileAppInstallEvent: ?{|
prompt: () => void,
userChoice: Promise<"accepted" | "dismissed">,
|},
listen: (() => void) => void,
mode: () => appInstallModes,
init: () => void,
trigger: () => boolean,
canInstall: () => boolean,
canInstallWithAppInstallEvent: () => boolean,
|};
/**
* Object that handles platform detection, support for and triggering of
* app installation events.
*
* App installation, or "add to homescreen" functionality is not in any way
* standardized. As such it differs from browser to browser. Chrome exposes an
* API for triggering it, while Firefox on Android as well as Safari require the
* user to do so themselves. Therefore, on those browsers, the user has to be
* presented with installation instructions. See the docs for `trigger`.
*/
const appInstall: appInstallMembers = {
listeners: [],
// This is the chrome-specific onbeforeinstallprompt event object
mobileAppInstallEvent: null,
/**
* Adds a listening callback that gets called if the canInstall() status changes.
* This is a no-op in anything but Chrome.
*/
listen(cb: () => void) {
if (window.onbeforeinstallprompt !== undefined) {
appInstall.listeners.push(cb);
}
},
/**
* Returns the installation mode, that is how an app will be installed.
* `null` means that we think it can't be installed, "chrome" uses the
* Chrome API, while firefox-android and safari-ios respectively refer
* to the browsers' manual installation method
*/
mode(): appInstallModes {
const UA = window.navigator.userAgent;
if (window.onbeforeinstallprompt !== undefined) {
// Browsers that use WebView will also have onbeforeinstallprompt, but
// it won't actually work. These browsers can be identified by the Version/
// string in the UA, which Chrome doesn't have.
if (UA.indexOf("Version/") !== -1) {
// Facebook in-app browser Android
if (/(Android).*(FBAN|FBAV|FB_IAB)/.test(UA)) {
return "facebook-android";
}
return "android-webview";
}
// Several Chrome-based browsers has onbeforeinstallprompt, but
// it's not actually functional. Thus, if we're on these browsers
// and we don't have a mobileAppInstallEvent, we return these other
// browsers as the type. If we do have mobileAppInstallEvent then
// this browser has native app installation support, and thus we
// should just use that (and in that case it's safe to return the
// browser as chrome).
if (appInstall.mobileAppInstallEvent === null) {
if (UA.indexOf("SamsungBrowser") !== -1) {
return "samsung";
}
if (UA.indexOf("OPR") !== -1) {
return "opera";
}
}
return "chrome";
}
// Unfortunately both Firefox and Firefox Focus use identical user
// agent strings. This means we're unable to differentiate Focus from
// normal Firefox. Thus, it's probably neccessary to provide a note in
// the Firefox instructions that it won't work with Firefox Focus.
if (/Android.*Firefox/.test(UA)) {
return "firefox-android";
}
// iPhone/iPad Safari (and Chrome+Firefox)
if (/(iphone|ip[ao]d).*safari/i.test(UA)) {
// Non-Safari browsers on iOS doesn't support apps, but they are
// all based upon the Safari rendering engine, so we detect them as
// Safari.
// Thus we need to sniff some additional stuff from the user agent:
// CriOS - Chrome iOS
// FxiOS - Firefox iOS
// GSA - Google Search App iOS
if (/((Cr|Fx)iOS|GSA)/.test(UA)) {
return "ios-other";
}
return "safari-ios";
}
// Facebook in-app browser iOS
if (/(iPhone|iP[ao]d).*(FBAN|FBAV|FB_IAB)/.test(UA)) {
return "facebook-ios";
}
return null;
},
/**
* Initializes event handlers. Should only ever be called once, and should
* be called as early as possible in the app lifetime so that no events are
* missed. This is a no-op on anything but Chrome.
*/
init() {
if (window.onbeforeinstallprompt !== undefined) {
window.addEventListener("beforeinstallprompt", (e) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// // Stash the event so it can be triggered later.
appInstall.mobileAppInstallEvent = e;
for (const listener of appInstall.listeners) {
try {
listener();
} catch (e) {} // eslint-disable-line no-empty
}
appInstall.listeners = [];
});
}
},
/**
* Returns a boolean. `true` if we can be installed as an app (and we're
* not already running in app mode), `false` otherwise. Note that in
* Chrome this can change if Chrome is still installing event handlers.
* Thus you should `.listen()` as well, to ensure you pick up any changes.
*/
canInstall(): boolean {
if (device.isAppMode() || !device.isMobileDevice()) {
return false;
}
return appInstall.mode() !== null;
},
/**
* Returns a boolean. `true` if we .canInstall() and can perform the
* installation using our .trigger() method. `false` otherwise.
*/
canInstallWithAppInstallEvent(): boolean {
if (!appInstall.canInstall()) {
return false;
}
return appInstall.mobileAppInstallEvent !== null;
},
/**
* Triggers installation of an app. It returns true if the installation was
* successfully triggered. If it returns false, then this is a browser that
* can't do native installation, and you should redirect the user to
* browser-specific instructions for installing as an app.
*
* The flow should be:
* 1. First .listen() for changes.
* 2. Check for canInstall(), if this is false, break, otherwise move on to
* Re-evaluate canInstall() whenever the listen() callback is called.
* 3. Add a menu option for installing the app. When clicked do:
* 1. Run trigger. If it returns true, then we're done, otherwise move to 2
* 2. Redirect to a page providing installation instructions, depending on `.mode()`
*/
trigger(): boolean {
if (
appInstall.mobileAppInstallEvent !== null &&
appInstall.mobileAppInstallEvent !== undefined
) {
appInstall.mobileAppInstallEvent.prompt();
// $FlowIgnore[incompatible-use] - thinks userChoice might have broken
appInstall.mobileAppInstallEvent.userChoice.then((outcome) => {
if (outcome === "accepted") {
appInstall.mobileAppInstallEvent = null;
}
});
return true;
}
return false;
},
};
export { appInstall };
|