UNPKG

@tidal-music/player

Version:
2,100 lines 79.8 kB
const U = { BTS: "application/vnd.tidal.bts", DASH: "application/dash+xml", EMU: "application/vnd.tidal.emu", HLS: "application/vnd.apple.mpegurl" }, se = "https://et.tidal.com/api/events", Oe = "https://et.tidal.com/api/public/events"; let z = Object.freeze({ apiUrl: "https://api.tidal.com/v1", appVersion: "TIDAL Player", clientPlatform: "web", desiredVolumeLevel: 1, eventUrl: se, gatherEvents: !0, loudnessNormalizationMode: "ALBUM", outputDevicesEnabled: !1, streamingWifiAudioQuality: "LOW" }); function y(t) { return z[t]; } function M(t) { return z = Object.freeze({ ...z, ...t }), Object.keys(t).forEach((e) => { const r = e; _e.dispatchEvent(new CustomEvent(r, { detail: t[r] })); }), z; } const _e = new EventTarget(); var ne = (t) => { throw TypeError(t); }, ie = (t, e, r) => e.has(t) || ne("Cannot " + r), k = (t, e, r) => (ie(t, e, "read from private field"), r ? r.call(t) : e.get(t)), W = (t, e, r) => e.has(t) ? ne("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(t) : e.set(t, r), F = (t, e, r, s) => (ie(t, e, "write to private field"), e.set(t, r), r), I, x, D, H; class Ne { constructor(e) { W(this, I), W(this, x, !1), W(this, D), W(this, H), F(this, H, new URL(e)), this.synchronize(); } /** * Returns the current time adjusted to server-time. * * @param clientCurrentTime The current time on the client side. Defaults to Date.now(). * @returns The current adjusted time (or the client time if not synced yet). */ // eslint-disable-next-line no-restricted-syntax now(e = Date.now()) { return !k(this, D) || !k(this, I) ? (console.warn("TrueTime is not yet synchronized"), e) : k(this, D) + (e - k(this, I)); } /** * Synchronizes the client's time with the server's time. * If the client's time is already synchronized within an hour, this method does nothing. * * @returns {Promise<void>} A promise that resolves when the synchronization is complete. */ async synchronize() { if (!(k(this, I) && // eslint-disable-next-line no-restricted-syntax Math.abs(Date.now() - k(this, I)) < 36e5 || k(this, x))) { F(this, x, !0); try { const e = await fetch(k(this, H)); e.ok && e.headers.has("date") && (F(this, D, new Date(e.headers.get("date")).getTime()), F(this, I, Date.now())); } catch (e) { console.error(e); } F(this, x, !1); } } /** * Returns the timestamp of a performance mark with the specified name and detail. * PS: `performance.mark` must be called with `startTime: trueTime.now()`. * * @param markName - The name of the performance mark. * @param detail - Optional. The detail of the performance mark. * @returns The timestamp of the performance mark, or undefined if not found. * @throws ReferenceError if the performance mark is not found. */ timestamp(e, r) { let s; if (r) { if (s = performance.getEntriesByName(e).find((i) => "detail" in i && i.detail === r), !s) throw new ReferenceError( `There is no performance entry named "${e}" with detail "${r}"` ); } else s = performance.getEntriesByName(e).pop(); return s ? s.startTime : void 0; } } I = /* @__PURE__ */ new WeakMap(), x = /* @__PURE__ */ new WeakMap(), D = /* @__PURE__ */ new WeakMap(), H = /* @__PURE__ */ new WeakMap(); const g = new Ne("https://api.tidal.com/v1/ping"), Be = (t, e, r) => { for (const s of t) { const i = s.addedNodes[0]; s.type === "childList" && i.id === e && r(); } }; function Ce() { const t = document.getElementById("tidal-player-root"); if (!t) return Promise.reject("No player root"); if ("video-one" in t.children) return Promise.resolve(); const e = []; return "video-one" in t.children || e.push( new Promise((r) => { new MutationObserver( (s) => Be(s, "video-one", r) ).observe(t, { childList: !0 }); }) ), Promise.all(e); } const O = document.createElement("video"), Le = (t) => { t.setAttribute("crossorigin", "anonymous"), t.setAttribute("playsinline", "playsinline"); }; Le(O); O.id = "video-one"; const Q = "tidal-player-root"; function Fe() { let t = document.getElementById(Q); if (!t) { const e = document.createElement("template"); e.id = Q, document.body.appendChild(e), t = document.getElementById(Q); } return Re(); } function Re() { const t = document.getElementById( Q ); return t && (O.id in t.children || t.appendChild(O)), Ce(); } function xe() { return new Promise( (t) => document.addEventListener( "click", () => { O.readyState === HTMLMediaElement.HAVE_NOTHING && !O.src && O.load(), t(); }, { once: !0 } ) ); } function De(t) { return new Promise((e) => setTimeout(() => e(), t)); } function Ue(t, e) { return new Promise((r) => { t.addEventListener(e, (s) => r(s), !1); }); } class We extends EventTarget { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - Setter #t; async dispatchAuthorized() { const e = await this.#t.getCredentials(); e?.token ? this.dispatchEvent( new CustomEvent("authorized", { detail: { credentials: e } }) ) : this.dispatchEvent( new CustomEvent("unauthenticated", { detail: { credentials: e } }) ); } set credentialsProvider(e) { this.#t = e, this.dispatchAuthorized().catch(console.error), this.#t.bus((r) => { switch (r.detail.type) { case "CredentialsUpdatedMessage": this.dispatchAuthorized().catch(console.error); break; default: console.warn("Unhandled event from credentials provider: ", r); break; } }); } get credentialsProvider() { return this.#t; } } class $ extends Error { errorCode = null; errorId = null; referenceId; constructor(e, r, s) { super(`${e}: ${r} (${s})`), this.errorId = e, this.errorCode = r, this.referenceId = s; } toJSON() { return { errorCode: this.errorCode, errorId: this.errorId, referenceId: this.referenceId }; } toString() { return JSON.stringify(this.toJSON()); } } async function B() { if (S.credentialsProvider) { const t = await S.credentialsProvider.getCredentials(); return !!(t.token && t.userId); } return await Ue(S, "authorized"), B(); } const S = new We(); async function Ve() { const t = await B(), e = async () => { const { Pushkin: s } = await Promise.resolve().then(() => Bt); return s.refresh(); }, r = async () => (await Promise.resolve().then(() => fe)).start(); if (t) { M({ eventUrl: se, gatherEvents: !0 }); try { await Promise.all([e(), r()]); } catch (s) { console.error(s); } } else M({ gatherEvents: !1 }); } async function ze() { const t = async () => (await Promise.resolve().then(() => fe)).start(); M({ eventUrl: Oe, gatherEvents: !0 }); try { await t(); } catch (e) { console.error(e); } } S.addEventListener("authorized", () => { Ve().then().catch(console.error); }); S.addEventListener("unauthenticated", () => { ze().then().catch(console.error); }); async function ee() { return S.credentialsProvider ? (await S.credentialsProvider.getCredentials()).token ?? null : null; } class He extends EventTarget { dispatchError(e) { this.dispatchEvent( new CustomEvent("error", { detail: e.toJSON() }) ); } } const P = new He(); class Qe { #t = /* @__PURE__ */ new Map(); #r = /* @__PURE__ */ new Map(); #s = /* @__PURE__ */ new Map(); #e = /* @__PURE__ */ new Map(); /** * Clones a session to re-use the media product and playback content for a new session. * * @param from - streamingSessionId to clone * @param to - target streamingSessionId */ clone(e, r) { const s = this.getMediaProductTransition(e), i = this.getStreamInfo(e), a = this.getPlaybackInfo(e); if (!s || !i || !a) throw new Error( `Cannot clone ${e}. Missing mediaProductTransition or streamInfo.` ); this.saveMediaProductTransition(r, { ...s, playbackContext: { ...s.playbackContext, playbackSessionId: r } }), this.saveStreamInfo(r, { ...i, streamingSessionId: r }), this.savePlaybackInfo(r, { ...a, streamingSessionId: r }); } deleteMediaProductTransition(e) { this.#t.delete(e); } /** * Removed cached playbackInfo, streamInfo and mediaProductTransition for session. * For re-using playbackInfo etc for other sessions make sure to run clone before deletion! * * @see {@link StreamingSessionStore.clone} */ deleteSession(e) { this.deleteMediaProductTransition(e), this.#r.delete(e), this.#s.delete(e), this.deleteStreamInfo(e); } deleteStartedStreamInfo(e) { this.#s.delete(e); } deleteStreamInfo(e) { this.#e.delete(e); } getMediaProductTransition(e) { return e ? this.#t.get(e) : void 0; } getPlaybackInfo(e) { return e ? this.#r.get(e) : void 0; } getStreamInfo(e) { return e ? this.#e.get(e) : void 0; } hasMediaProductTransition(e) { return this.#t.has(e); } hasPlaybackInfo(e) { return this.#r.has(e); } hasStartedStreamInfo(e) { return this.#s.has(e); } hasStreamInfo(e) { return this.#e.has(e); } overwriteDuration(e, r) { const s = this.#t.get(e); s && (s.playbackContext = { ...s.playbackContext, actualDuration: r }, this.saveMediaProductTransition(e, s)); } saveMediaProductTransition(e, r) { this.#t.set( e, r ); } savePlaybackInfo(e, r) { this.#r.set(e, r); } saveStreamInfo(e, r) { this.#e.set(e, r); } setStartedStreamInfo(e) { return this.#s.set(e, !0); } } const E = new Qe(); class $e { activePlayer; preloadPlayer; get preloadedMediaProduct() { return E.getMediaProductTransition( this.preloadedStreamingSessionId )?.mediaProduct ?? void 0; } get preloadedStreamingSessionId() { return this.preloadPlayer?.preloadedStreamingSessionId ?? void 0; } } const l = new $e(); let K; const Ge = [ { itemTypes: ["track", "video", "demo"], player: "shaka", qualities: ["HIGH", "LOSSLESS", "LOW", "HI_RES_LOSSLESS"] } ]; let C = Ge; const w = { browser: void 0, native: void 0, shaka: void 0 }; function je(t) { C = t; } async function qe() { await Promise.all( C.map((t) => { const e = w[t.player]; return e ? e.reset() : Promise.resolve(); }) ), l.activePlayer = void 0, l.preloadPlayer = void 0; } function X(t) { t.name !== "nativePlayer" && w.native && w.native.abandon(), l.activePlayer = t; } async function Ke(t) { X(t), await Promise.all( C.filter((e) => w[e.player] !== l.activePlayer).map(async (e) => { const r = w[e.player]; return r && await r.reset(), Promise.resolve(); }) ), await l.activePlayer?.skipToPreloadedMediaProduct(), await l.activePlayer?.play(); } function ae() { P.removeEventListener("ended", K); } function Je(t) { l.activePlayer && t.name === l.activePlayer.name || (K = () => { Ke(t).then().catch(console.error); }, P.addEventListener("ended", K, { once: !0 })); } async function oe() { const { default: t } = await import("./nativePlayer-CPyCegnV.js"); return w.native || (w.native = new t()), w.native; } async function Ye() { const { default: t } = await import("./browserPlayer-CIpjt2eR.js"); return w.browser || (w.browser = new t()), w.browser; } async function Xe() { const { default: t } = await import("./shakaPlayer-Bl5ADZ6B.js"); return w.shaka || (w.shaka = new t()), w.shaka; } async function ce(t, e) { const r = C.filter((i) => i.itemTypes.includes(t)).filter( (i) => t === "track" && i.qualities && e ? i.qualities.includes(e) : !0 ); r.length === 0 && console.error( `No player found to handle audio quality "${String( e )}" for product type "${t}" in player config: `, C ); const { player: s } = r[0]; switch (s) { case "browser": return Ye(); case "native": return oe(); case "shaka": return Xe(); default: throw new Error("No player found."); } } async function Ze() { await Promise.all( C.map((t) => { const e = w[t.player]; return e ? e.unloadPreloadedMediaProduct() : Promise.resolve(); }) ); } function zt() { return l.activePlayer?.getPosition() ?? 0; } function Ht() { return y("loudnessNormalizationMode"); } function Qt() { return l.activePlayer?.currentMediaProduct ?? null; } function $t() { return l.preloadedMediaProduct ?? null; } async function Gt() { const { outputDevices: t } = await import("./output-devices-B2CSTw2l.js"); return [...t.outputDevices]; } function jt() { const t = l.activePlayer; if (t) { const e = E.getMediaProductTransition( t.currentStreamingSessionId ); if (e) return e.playbackContext; } } function qt() { return l.activePlayer?.playbackState ?? "IDLE"; } function Kt() { return "0.7.0"; } function Jt() { return y("streamingWifiAudioQuality"); } function Yt() { return y("desiredVolumeLevel"); } class et { #t; constructor() { this.#t = /* @__PURE__ */ new Map(); } async #r(e, r) { const s = new TextEncoder().encode( `${e}-${r}` ), i = await crypto.subtle.digest("SHA-1", s); return Array.from(new Uint8Array(i)).map((d) => d.toString(16).padStart(2, "0")).join(""); } /** * Delete a logged event by name and streamingSessionId. */ async delete({ name: e, streamingSessionId: r }) { const s = await this.#r( r, e ); this.#t.delete(s); } /** * Get a logged event by name and streamingSessionId. */ async get({ name: e, streamingSessionId: r }) { const s = await this.#r( r, e ); return this.#t.get(s); } /** * Adds or updates an event. */ async put(e) { const r = await this.#r( e.streamingSessionId, e.name ); this.#t.set(r, e); } } const J = new et(); async function L(t, e) { return y("gatherEvents") ? async (r) => { try { const s = await J.get({ name: t, streamingSessionId: r.streamingSessionId }), i = Object.assign( {}, e, s ? s.payload : {}, r ); for (const a of Object.keys(r)) { const o = a; Array.isArray(r[o]) && (i[o] = [ ...e[o] || [], ...s && s.payload[o] || [], ...r[o] || [] ]); } return await J.put({ name: t, payload: i, streamingSessionId: r.streamingSessionId }), { name: t, payload: i, streamingSessionId: r.streamingSessionId }; } catch (s) { console.error(s); } } : (r) => Promise.resolve(void 0); } const tt = { endReason: "COMPLETE", endTimestamp: 0, errorCode: null, errorMessage: null, startTimestamp: 0, streamingSessionId: "" }, rt = await L( "drm_license_fetch", tt ); function Xt(t) { return rt(t); } const st = { endReason: "COMPLETE", endTimestamp: 0, errorCode: null, errorMessage: null, startTimestamp: 0, streamingSessionId: "" }, nt = await L( "playback_info_fetch", st ); function G(t) { return nt(t); } function Zt(t) { switch (t) { case "bluetooth": return "BLUETOOTH"; case "builtIn": return "BUILT_IN"; case "displayPort": return "DISPLAY_PORT"; case "hdmi": return "HDMI"; case "systemDefault": return "SYSTEM_DEFAULT"; case "usb": return "USB"; // Never happens (cannot be chose by user) case "airplay": case "windowsCommunication": default: return; } } const it = { actualAssetPresentation: "FULL", actualAudioMode: "STEREO", actualProductId: null, actualQuality: "LOSSLESS", actualStartTimestamp: 0, actualStreamType: "ON_DEMAND", adaptations: [], cdm: "WIDEVINE", cdmVersion: null, endReason: "COMPLETE", endTimestamp: 0, errorCode: null, errorMessage: null, hasAds: !1, idealStartTimestamp: 0, outputDevice: null, productType: "TRACK", stalls: [], streamingSessionId: "" }, at = await L( "playback_statistics", it ); function te(t) { return at(t); } const ot = { streamingSessionId: "", timestamp: 0 }, ct = await L( "streaming_session_end", ot ); function dt(t) { return ct(t); } const lt = { "Amazon Silk": "amazon_silk", "Android Browser": "android", Bada: "bada", BlackBerry: "blackberry", Chrome: "chrome", Chromium: "chromium", Electron: "electron", Epiphany: "epiphany", Firefox: "firefox", Focus: "focus", Generic: "generic", "Google Search": "google_search", Googlebot: "googlebot", "Internet Explorer": "ie", "K-Meleon": "k_meleon", Maxthon: "maxthon", "Microsoft Edge": "edge", "MZ Browser": "mz", "NAVER Whale Browser": "naver", Opera: "opera", "Opera Coast": "opera_coast", PhantomJS: "phantomjs", Puffin: "puffin", QupZilla: "qupzilla", QQ: "qq", QQLite: "qqlite", Safari: "safari", Sailfish: "sailfish", "Samsung Internet for Android": "samsung_internet", SeaMonkey: "seamonkey", Sleipnir: "sleipnir", Swing: "swing", Tizen: "tizen", "UC Browser": "uc", Vivaldi: "vivaldi", "WebOS Browser": "webos", WeChat: "wechat", "Yandex Browser": "yandex", Roku: "roku" }, de = { amazon_silk: "Amazon Silk", android: "Android Browser", bada: "Bada", blackberry: "BlackBerry", chrome: "Chrome", chromium: "Chromium", electron: "Electron", epiphany: "Epiphany", firefox: "Firefox", focus: "Focus", generic: "Generic", googlebot: "Googlebot", google_search: "Google Search", ie: "Internet Explorer", k_meleon: "K-Meleon", maxthon: "Maxthon", edge: "Microsoft Edge", mz: "MZ Browser", naver: "NAVER Whale Browser", opera: "Opera", opera_coast: "Opera Coast", phantomjs: "PhantomJS", puffin: "Puffin", qupzilla: "QupZilla", qq: "QQ Browser", qqlite: "QQ Browser Lite", safari: "Safari", sailfish: "Sailfish", samsung_internet: "Samsung Internet for Android", seamonkey: "SeaMonkey", sleipnir: "Sleipnir", swing: "Swing", tizen: "Tizen", uc: "UC Browser", vivaldi: "Vivaldi", webos: "WebOS Browser", wechat: "WeChat", yandex: "Yandex Browser" }, m = { tablet: "tablet", mobile: "mobile", desktop: "desktop", tv: "tv" }, v = { WindowsPhone: "Windows Phone", Windows: "Windows", MacOS: "macOS", iOS: "iOS", Android: "Android", WebOS: "WebOS", BlackBerry: "BlackBerry", Bada: "Bada", Tizen: "Tizen", Linux: "Linux", ChromeOS: "Chrome OS", PlayStation4: "PlayStation 4", Roku: "Roku" }, T = { EdgeHTML: "EdgeHTML", Blink: "Blink", Trident: "Trident", Presto: "Presto", Gecko: "Gecko", WebKit: "WebKit" }; class n { /** * Get first matched item for a string * @param {RegExp} regexp * @param {String} ua * @return {Array|{index: number, input: string}|*|boolean|string} */ static getFirstMatch(e, r) { const s = r.match(e); return s && s.length > 0 && s[1] || ""; } /** * Get second matched item for a string * @param regexp * @param {String} ua * @return {Array|{index: number, input: string}|*|boolean|string} */ static getSecondMatch(e, r) { const s = r.match(e); return s && s.length > 1 && s[2] || ""; } /** * Match a regexp and return a constant or undefined * @param {RegExp} regexp * @param {String} ua * @param {*} _const Any const that will be returned if regexp matches the string * @return {*} */ static matchAndReturnConst(e, r, s) { if (e.test(r)) return s; } static getWindowsVersionName(e) { switch (e) { case "NT": return "NT"; case "XP": return "XP"; case "NT 5.0": return "2000"; case "NT 5.1": return "XP"; case "NT 5.2": return "2003"; case "NT 6.0": return "Vista"; case "NT 6.1": return "7"; case "NT 6.2": return "8"; case "NT 6.3": return "8.1"; case "NT 10.0": return "10"; default: return; } } /** * Get macOS version name * 10.5 - Leopard * 10.6 - Snow Leopard * 10.7 - Lion * 10.8 - Mountain Lion * 10.9 - Mavericks * 10.10 - Yosemite * 10.11 - El Capitan * 10.12 - Sierra * 10.13 - High Sierra * 10.14 - Mojave * 10.15 - Catalina * * @example * getMacOSVersionName("10.14") // 'Mojave' * * @param {string} version * @return {string} versionName */ static getMacOSVersionName(e) { const r = e.split(".").splice(0, 2).map((s) => parseInt(s, 10) || 0); if (r.push(0), r[0] === 10) switch (r[1]) { case 5: return "Leopard"; case 6: return "Snow Leopard"; case 7: return "Lion"; case 8: return "Mountain Lion"; case 9: return "Mavericks"; case 10: return "Yosemite"; case 11: return "El Capitan"; case 12: return "Sierra"; case 13: return "High Sierra"; case 14: return "Mojave"; case 15: return "Catalina"; default: return; } } /** * Get Android version name * 1.5 - Cupcake * 1.6 - Donut * 2.0 - Eclair * 2.1 - Eclair * 2.2 - Froyo * 2.x - Gingerbread * 3.x - Honeycomb * 4.0 - Ice Cream Sandwich * 4.1 - Jelly Bean * 4.4 - KitKat * 5.x - Lollipop * 6.x - Marshmallow * 7.x - Nougat * 8.x - Oreo * 9.x - Pie * * @example * getAndroidVersionName("7.0") // 'Nougat' * * @param {string} version * @return {string} versionName */ static getAndroidVersionName(e) { const r = e.split(".").splice(0, 2).map((s) => parseInt(s, 10) || 0); if (r.push(0), !(r[0] === 1 && r[1] < 5)) { if (r[0] === 1 && r[1] < 6) return "Cupcake"; if (r[0] === 1 && r[1] >= 6) return "Donut"; if (r[0] === 2 && r[1] < 2) return "Eclair"; if (r[0] === 2 && r[1] === 2) return "Froyo"; if (r[0] === 2 && r[1] > 2) return "Gingerbread"; if (r[0] === 3) return "Honeycomb"; if (r[0] === 4 && r[1] < 1) return "Ice Cream Sandwich"; if (r[0] === 4 && r[1] < 4) return "Jelly Bean"; if (r[0] === 4 && r[1] >= 4) return "KitKat"; if (r[0] === 5) return "Lollipop"; if (r[0] === 6) return "Marshmallow"; if (r[0] === 7) return "Nougat"; if (r[0] === 8) return "Oreo"; if (r[0] === 9) return "Pie"; } } /** * Get version precisions count * * @example * getVersionPrecision("1.10.3") // 3 * * @param {string} version * @return {number} */ static getVersionPrecision(e) { return e.split(".").length; } /** * Calculate browser version weight * * @example * compareVersions('1.10.2.1', '1.8.2.1.90') // 1 * compareVersions('1.010.2.1', '1.09.2.1.90'); // 1 * compareVersions('1.10.2.1', '1.10.2.1'); // 0 * compareVersions('1.10.2.1', '1.0800.2'); // -1 * compareVersions('1.10.2.1', '1.10', true); // 0 * * @param {String} versionA versions versions to compare * @param {String} versionB versions versions to compare * @param {boolean} [isLoose] enable loose comparison * @return {Number} comparison result: -1 when versionA is lower, * 1 when versionA is bigger, 0 when both equal */ /* eslint consistent-return: 1 */ static compareVersions(e, r, s = !1) { const i = n.getVersionPrecision(e), a = n.getVersionPrecision(r); let o = Math.max(i, a), d = 0; const c = n.map([e, r], (h) => { const f = o - n.getVersionPrecision(h), b = h + new Array(f + 1).join(".0"); return n.map(b.split("."), (p) => new Array(20 - p.length).join("0") + p).reverse(); }); for (s && (d = o - Math.min(i, a)), o -= 1; o >= d; ) { if (c[0][o] > c[1][o]) return 1; if (c[0][o] === c[1][o]) { if (o === d) return 0; o -= 1; } else if (c[0][o] < c[1][o]) return -1; } } /** * Array::map polyfill * * @param {Array} arr * @param {Function} iterator * @return {Array} */ static map(e, r) { const s = []; let i; if (Array.prototype.map) return Array.prototype.map.call(e, r); for (i = 0; i < e.length; i += 1) s.push(r(e[i])); return s; } /** * Array::find polyfill * * @param {Array} arr * @param {Function} predicate * @return {Array} */ static find(e, r) { let s, i; if (Array.prototype.find) return Array.prototype.find.call(e, r); for (s = 0, i = e.length; s < i; s += 1) { const a = e[s]; if (r(a, s)) return a; } } /** * Object::assign polyfill * * @param {Object} obj * @param {Object} ...objs * @return {Object} */ static assign(e, ...r) { const s = e; let i, a; if (Object.assign) return Object.assign(e, ...r); for (i = 0, a = r.length; i < a; i += 1) { const o = r[i]; typeof o == "object" && o !== null && Object.keys(o).forEach((c) => { s[c] = o[c]; }); } return e; } /** * Get short version/alias for a browser name * * @example * getBrowserAlias('Microsoft Edge') // edge * * @param {string} browserName * @return {string} */ static getBrowserAlias(e) { return lt[e]; } /** * Get short version/alias for a browser name * * @example * getBrowserAlias('edge') // Microsoft Edge * * @param {string} browserAlias * @return {string} */ static getBrowserTypeByAlias(e) { return de[e] || ""; } } const u = /version\/(\d+(\.?_?\d+)+)/i, ut = [ /* Googlebot */ { test: [/googlebot/i], describe(t) { const e = { name: "Googlebot" }, r = n.getFirstMatch(/googlebot\/(\d+(\.\d+))/i, t) || n.getFirstMatch(u, t); return r && (e.version = r), e; } }, /* Opera < 13.0 */ { test: [/opera/i], describe(t) { const e = { name: "Opera" }, r = n.getFirstMatch(u, t) || n.getFirstMatch(/(?:opera)[\s/](\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, /* Opera > 13.0 */ { test: [/opr\/|opios/i], describe(t) { const e = { name: "Opera" }, r = n.getFirstMatch(/(?:opr|opios)[\s/](\S+)/i, t) || n.getFirstMatch(u, t); return r && (e.version = r), e; } }, { test: [/SamsungBrowser/i], describe(t) { const e = { name: "Samsung Internet for Android" }, r = n.getFirstMatch(u, t) || n.getFirstMatch(/(?:SamsungBrowser)[\s/](\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/Whale/i], describe(t) { const e = { name: "NAVER Whale Browser" }, r = n.getFirstMatch(u, t) || n.getFirstMatch(/(?:whale)[\s/](\d+(?:\.\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/MZBrowser/i], describe(t) { const e = { name: "MZ Browser" }, r = n.getFirstMatch(/(?:MZBrowser)[\s/](\d+(?:\.\d+)+)/i, t) || n.getFirstMatch(u, t); return r && (e.version = r), e; } }, { test: [/focus/i], describe(t) { const e = { name: "Focus" }, r = n.getFirstMatch(/(?:focus)[\s/](\d+(?:\.\d+)+)/i, t) || n.getFirstMatch(u, t); return r && (e.version = r), e; } }, { test: [/swing/i], describe(t) { const e = { name: "Swing" }, r = n.getFirstMatch(/(?:swing)[\s/](\d+(?:\.\d+)+)/i, t) || n.getFirstMatch(u, t); return r && (e.version = r), e; } }, { test: [/coast/i], describe(t) { const e = { name: "Opera Coast" }, r = n.getFirstMatch(u, t) || n.getFirstMatch(/(?:coast)[\s/](\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/opt\/\d+(?:.?_?\d+)+/i], describe(t) { const e = { name: "Opera Touch" }, r = n.getFirstMatch(/(?:opt)[\s/](\d+(\.?_?\d+)+)/i, t) || n.getFirstMatch(u, t); return r && (e.version = r), e; } }, { test: [/yabrowser/i], describe(t) { const e = { name: "Yandex Browser" }, r = n.getFirstMatch(/(?:yabrowser)[\s/](\d+(\.?_?\d+)+)/i, t) || n.getFirstMatch(u, t); return r && (e.version = r), e; } }, { test: [/ucbrowser/i], describe(t) { const e = { name: "UC Browser" }, r = n.getFirstMatch(u, t) || n.getFirstMatch(/(?:ucbrowser)[\s/](\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/Maxthon|mxios/i], describe(t) { const e = { name: "Maxthon" }, r = n.getFirstMatch(u, t) || n.getFirstMatch(/(?:Maxthon|mxios)[\s/](\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/epiphany/i], describe(t) { const e = { name: "Epiphany" }, r = n.getFirstMatch(u, t) || n.getFirstMatch(/(?:epiphany)[\s/](\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/puffin/i], describe(t) { const e = { name: "Puffin" }, r = n.getFirstMatch(u, t) || n.getFirstMatch(/(?:puffin)[\s/](\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/sleipnir/i], describe(t) { const e = { name: "Sleipnir" }, r = n.getFirstMatch(u, t) || n.getFirstMatch(/(?:sleipnir)[\s/](\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/k-meleon/i], describe(t) { const e = { name: "K-Meleon" }, r = n.getFirstMatch(u, t) || n.getFirstMatch(/(?:k-meleon)[\s/](\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/micromessenger/i], describe(t) { const e = { name: "WeChat" }, r = n.getFirstMatch(/(?:micromessenger)[\s/](\d+(\.?_?\d+)+)/i, t) || n.getFirstMatch(u, t); return r && (e.version = r), e; } }, { test: [/qqbrowser/i], describe(t) { const e = { name: /qqbrowserlite/i.test(t) ? "QQ Browser Lite" : "QQ Browser" }, r = n.getFirstMatch(/(?:qqbrowserlite|qqbrowser)[/](\d+(\.?_?\d+)+)/i, t) || n.getFirstMatch(u, t); return r && (e.version = r), e; } }, { test: [/msie|trident/i], describe(t) { const e = { name: "Internet Explorer" }, r = n.getFirstMatch(/(?:msie |rv:)(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/\sedg\//i], describe(t) { const e = { name: "Microsoft Edge" }, r = n.getFirstMatch(/\sedg\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/edg([ea]|ios)/i], describe(t) { const e = { name: "Microsoft Edge" }, r = n.getSecondMatch(/edg([ea]|ios)\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/vivaldi/i], describe(t) { const e = { name: "Vivaldi" }, r = n.getFirstMatch(/vivaldi\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/seamonkey/i], describe(t) { const e = { name: "SeaMonkey" }, r = n.getFirstMatch(/seamonkey\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/sailfish/i], describe(t) { const e = { name: "Sailfish" }, r = n.getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i, t); return r && (e.version = r), e; } }, { test: [/silk/i], describe(t) { const e = { name: "Amazon Silk" }, r = n.getFirstMatch(/silk\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/phantom/i], describe(t) { const e = { name: "PhantomJS" }, r = n.getFirstMatch(/phantomjs\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/slimerjs/i], describe(t) { const e = { name: "SlimerJS" }, r = n.getFirstMatch(/slimerjs\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/blackberry|\bbb\d+/i, /rim\stablet/i], describe(t) { const e = { name: "BlackBerry" }, r = n.getFirstMatch(u, t) || n.getFirstMatch(/blackberry[\d]+\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/(web|hpw)[o0]s/i], describe(t) { const e = { name: "WebOS Browser" }, r = n.getFirstMatch(u, t) || n.getFirstMatch(/w(?:eb)?[o0]sbrowser\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/bada/i], describe(t) { const e = { name: "Bada" }, r = n.getFirstMatch(/dolfin\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/tizen/i], describe(t) { const e = { name: "Tizen" }, r = n.getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.?_?\d+)+)/i, t) || n.getFirstMatch(u, t); return r && (e.version = r), e; } }, { test: [/qupzilla/i], describe(t) { const e = { name: "QupZilla" }, r = n.getFirstMatch(/(?:qupzilla)[\s/](\d+(\.?_?\d+)+)/i, t) || n.getFirstMatch(u, t); return r && (e.version = r), e; } }, { test: [/firefox|iceweasel|fxios/i], describe(t) { const e = { name: "Firefox" }, r = n.getFirstMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/electron/i], describe(t) { const e = { name: "Electron" }, r = n.getFirstMatch(/(?:electron)\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/MiuiBrowser/i], describe(t) { const e = { name: "Miui" }, r = n.getFirstMatch(/(?:MiuiBrowser)[\s/](\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/chromium/i], describe(t) { const e = { name: "Chromium" }, r = n.getFirstMatch(/(?:chromium)[\s/](\d+(\.?_?\d+)+)/i, t) || n.getFirstMatch(u, t); return r && (e.version = r), e; } }, { test: [/chrome|crios|crmo/i], describe(t) { const e = { name: "Chrome" }, r = n.getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, { test: [/GSA/i], describe(t) { const e = { name: "Google Search" }, r = n.getFirstMatch(/(?:GSA)\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, /* Android Browser */ { test(t) { const e = !t.test(/like android/i), r = t.test(/android/i); return e && r; }, describe(t) { const e = { name: "Android Browser" }, r = n.getFirstMatch(u, t); return r && (e.version = r), e; } }, /* PlayStation 4 */ { test: [/playstation 4/i], describe(t) { const e = { name: "PlayStation 4" }, r = n.getFirstMatch(u, t); return r && (e.version = r), e; } }, /* Safari */ { test: [/safari|applewebkit/i], describe(t) { const e = { name: "Safari" }, r = n.getFirstMatch(u, t); return r && (e.version = r), e; } }, /* Something else */ { test: [/.*/i], describe(t) { const e = /^(.*)\/(.*) /, r = /^(.*)\/(.*)[ \t]\((.*)/, i = t.search("\\(") !== -1 ? r : e; return { name: n.getFirstMatch(i, t), version: n.getSecondMatch(i, t) }; } } ], ht = [ /* Roku */ { test: [/Roku\/DVP/], describe(t) { const e = n.getFirstMatch(/Roku\/DVP-(\d+\.\d+)/i, t); return { name: v.Roku, version: e }; } }, /* Windows Phone */ { test: [/windows phone/i], describe(t) { const e = n.getFirstMatch(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i, t); return { name: v.WindowsPhone, version: e }; } }, /* Windows */ { test: [/windows /i], describe(t) { const e = n.getFirstMatch(/Windows ((NT|XP)( \d\d?.\d)?)/i, t), r = n.getWindowsVersionName(e); return { name: v.Windows, version: e, versionName: r }; } }, /* Firefox on iPad */ { test: [/Macintosh(.*?) FxiOS(.*?)\//], describe(t) { const e = { name: v.iOS }, r = n.getSecondMatch(/(Version\/)(\d[\d.]+)/, t); return r && (e.version = r), e; } }, /* macOS */ { test: [/macintosh/i], describe(t) { const e = n.getFirstMatch(/mac os x (\d+(\.?_?\d+)+)/i, t).replace(/[_\s]/g, "."), r = n.getMacOSVersionName(e), s = { name: v.MacOS, version: e }; return r && (s.versionName = r), s; } }, /* iOS */ { test: [/(ipod|iphone|ipad)/i], describe(t) { const e = n.getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i, t).replace(/[_\s]/g, "."); return { name: v.iOS, version: e }; } }, /* Android */ { test(t) { const e = !t.test(/like android/i), r = t.test(/android/i); return e && r; }, describe(t) { const e = n.getFirstMatch(/android[\s/-](\d+(\.\d+)*)/i, t), r = n.getAndroidVersionName(e), s = { name: v.Android, version: e }; return r && (s.versionName = r), s; } }, /* WebOS */ { test: [/(web|hpw)[o0]s/i], describe(t) { const e = n.getFirstMatch(/(?:web|hpw)[o0]s\/(\d+(\.\d+)*)/i, t), r = { name: v.WebOS }; return e && e.length && (r.version = e), r; } }, /* BlackBerry */ { test: [/blackberry|\bbb\d+/i, /rim\stablet/i], describe(t) { const e = n.getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i, t) || n.getFirstMatch(/blackberry\d+\/(\d+([_\s]\d+)*)/i, t) || n.getFirstMatch(/\bbb(\d+)/i, t); return { name: v.BlackBerry, version: e }; } }, /* Bada */ { test: [/bada/i], describe(t) { const e = n.getFirstMatch(/bada\/(\d+(\.\d+)*)/i, t); return { name: v.Bada, version: e }; } }, /* Tizen */ { test: [/tizen/i], describe(t) { const e = n.getFirstMatch(/tizen[/\s](\d+(\.\d+)*)/i, t); return { name: v.Tizen, version: e }; } }, /* Linux */ { test: [/linux/i], describe() { return { name: v.Linux }; } }, /* Chrome OS */ { test: [/CrOS/], describe() { return { name: v.ChromeOS }; } }, /* Playstation 4 */ { test: [/PlayStation 4/], describe(t) { const e = n.getFirstMatch(/PlayStation 4[/\s](\d+(\.\d+)*)/i, t); return { name: v.PlayStation4, version: e }; } } ], mt = [ /* Googlebot */ { test: [/googlebot/i], describe() { return { type: "bot", vendor: "Google" }; } }, /* Huawei */ { test: [/huawei/i], describe(t) { const e = n.getFirstMatch(/(can-l01)/i, t) && "Nova", r = { type: m.mobile, vendor: "Huawei" }; return e && (r.model = e), r; } }, /* Nexus Tablet */ { test: [/nexus\s*(?:7|8|9|10).*/i], describe() { return { type: m.tablet, vendor: "Nexus" }; } }, /* iPad */ { test: [/ipad/i], describe() { return { type: m.tablet, vendor: "Apple", model: "iPad" }; } }, /* Firefox on iPad */ { test: [/Macintosh(.*?) FxiOS(.*?)\//], describe() { return { type: m.tablet, vendor: "Apple", model: "iPad" }; } }, /* Amazon Kindle Fire */ { test: [/kftt build/i], describe() { return { type: m.tablet, vendor: "Amazon", model: "Kindle Fire HD 7" }; } }, /* Another Amazon Tablet with Silk */ { test: [/silk/i], describe() { return { type: m.tablet, vendor: "Amazon" }; } }, /* Tablet */ { test: [/tablet(?! pc)/i], describe() { return { type: m.tablet }; } }, /* iPod/iPhone */ { test(t) { const e = t.test(/ipod|iphone/i), r = t.test(/like (ipod|iphone)/i); return e && !r; }, describe(t) { const e = n.getFirstMatch(/(ipod|iphone)/i, t); return { type: m.mobile, vendor: "Apple", model: e }; } }, /* Nexus Mobile */ { test: [/nexus\s*[0-6].*/i, /galaxy nexus/i], describe() { return { type: m.mobile, vendor: "Nexus" }; } }, /* Mobile */ { test: [/[^-]mobi/i], describe() { return { type: m.mobile }; } }, /* BlackBerry */ { test(t) { return t.getBrowserName(!0) === "blackberry"; }, describe() { return { type: m.mobile, vendor: "BlackBerry" }; } }, /* Bada */ { test(t) { return t.getBrowserName(!0) === "bada"; }, describe() { return { type: m.mobile }; } }, /* Windows Phone */ { test(t) { return t.getBrowserName() === "windows phone"; }, describe() { return { type: m.mobile, vendor: "Microsoft" }; } }, /* Android Tablet */ { test(t) { const e = Number(String(t.getOSVersion()).split(".")[0]); return t.getOSName(!0) === "android" && e >= 3; }, describe() { return { type: m.tablet }; } }, /* Android Mobile */ { test(t) { return t.getOSName(!0) === "android"; }, describe() { return { type: m.mobile }; } }, /* desktop */ { test(t) { return t.getOSName(!0) === "macos"; }, describe() { return { type: m.desktop, vendor: "Apple" }; } }, /* Windows */ { test(t) { return t.getOSName(!0) === "windows"; }, describe() { return { type: m.desktop }; } }, /* Linux */ { test(t) { return t.getOSName(!0) === "linux"; }, describe() { return { type: m.desktop }; } }, /* PlayStation 4 */ { test(t) { return t.getOSName(!0) === "playstation 4"; }, describe() { return { type: m.tv }; } }, /* Roku */ { test(t) { return t.getOSName(!0) === "roku"; }, describe() { return { type: m.tv }; } } ], ft = [ /* EdgeHTML */ { test(t) { return t.getBrowserName(!0) === "microsoft edge"; }, describe(t) { if (/\sedg\//i.test(t)) return { name: T.Blink }; const r = n.getFirstMatch(/edge\/(\d+(\.?_?\d+)+)/i, t); return { name: T.EdgeHTML, version: r }; } }, /* Trident */ { test: [/trident/i], describe(t) { const e = { name: T.Trident }, r = n.getFirstMatch(/trident\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, /* Presto */ { test(t) { return t.test(/presto/i); }, describe(t) { const e = { name: T.Presto }, r = n.getFirstMatch(/presto\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, /* Gecko */ { test(t) { const e = t.test(/gecko/i), r = t.test(/like gecko/i); return e && !r; }, describe(t) { const e = { name: T.Gecko }, r = n.getFirstMatch(/gecko\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } }, /* Blink */ { test: [/(apple)?webkit\/537\.36/i], describe() { return { name: T.Blink }; } }, /* WebKit */ { test: [/(apple)?webkit/i], describe(t) { const e = { name: T.WebKit }, r = n.getFirstMatch(/webkit\/(\d+(\.?_?\d+)+)/i, t); return r && (e.version = r), e; } } ]; class re { /** * Create instance of Parser * * @param {String} UA User-Agent string * @param {Boolean} [skipParsing=false] parser can skip parsing in purpose of performance * improvements if you need to make a more particular parsing * like {@link Parser#parseBrowser} or {@link Parser#parsePlatform} * * @throw {Error} in case of empty UA String * * @constructor */ constructor(e, r = !1) { if (e == null || e === "") throw new Error("UserAgent parameter can't be empty"); this._ua = e, this.parsedResult = {}, r !== !0 && this.parse(); } /** * Get UserAgent string of current Parser instance * @return {String} User-Agent String of the current <Parser> object * * @public */ getUA() { return this._ua; } /** * Test a UA string for a regexp * @param {RegExp} regex * @return {Boolean} */ test(e) { return e.test(this._ua); } /** * Get parsed browser object * @return {Object} */ parseBrowser() { this.parsedResult.browser = {}; const e = n.find(ut, (r) => { if (typeof r.test == "function") return r.test(this); if (r.test instanceof Array) return r.test.some((s) => this.test(s)); throw new Error("Browser's test function is not valid"); }); return e && (this.parsedResult.browser = e.describe(this.getUA())), this.parsedResult.browser; } /** * Get parsed browser object * @return {Object} * * @public */ getBrowser() { return this.parsedResult.browser ? this.parsedResult.browser : this.parseBrowser(); } /** * Get browser's name * @return {String} Browser's name or an empty string * * @public */ getBrowserName(e) { return e ? String(this.getBrowser().name).toLowerCase() || "" : this.getBrowser().name || ""; } /** * Get browser's version * @return {String} version of browser * * @public */ getBrowserVersion() { return this.getBrowser().version; } /** * Get OS * @return {Object} * * @example * this.getOS(); * { * name: 'macOS', * version: '10.11.12' * } */ getOS() { return this.parsedResult.os ? this.parsedResult.os : this.parseOS(); } /** * Parse OS and save it to this.parsedResult.os * @return {*|{}} */ parseOS() { this.parsedResult.os = {}; const e = n.find(ht, (r) => { if (typeof r.test == "function") return r.test(this); if (r.test instanceof Array) return r.test.some((s) => this.test(s)); throw new Error("Browser's test function is not valid"); }); return e && (this.parsedResult.os = e.describe(this.getUA())), this.parsedResult.os; } /** * Get OS name * @param {Boolean} [toLowerCase] return lower-cased value * @return {String} name of the OS — macOS, Windows, Linux, etc. */ getOSName(e) { const { name: r } = this.getOS(); return e ? String(r).toLowerCase() || "" : r || ""; } /** * Get OS version * @return {String} full version with dots ('10.11.12', '5.6', etc) */ getOSVersion() { return this.getOS().version; } /** * Get parsed platform * @return {{}} */ getPlatform() { return this.parsedResult.platform ? this.parsedResult.platform : this.parsePlatform(); } /** * Get platform name * @param {Boolean} [toLowerCase=false] * @return {*} */ getPlatformType(e = !1) { const { type: r } = this.getPlatform(); return e ? String(r).toLowerCase() || "" : r || ""; } /** * Get parsed platform * @return {{}} */ parsePlatform() { this.parsedResult.platform = {}; const e = n.find(mt, (r) => { if (typeof r.test == "function") return r.test(this); if (r.test instanceof Array) return r.test.some((s) => this.test(s)); throw new Error("Browser's test function is not valid"); }); return e && (this.parsedResult.platform = e.describe(this.getUA())), this.parsedResult.platform; } /** * Get parsed engine * @return {{}} */ getEngine() { return this.parsedResult.engine ? this.parsedResult.engine : this.parseEngine(); } /** * Get engines's name * @return {String} Engines's name or an empty string * * @public */ getEngineName(e) { return e ? String(this.getEngine().name).toLowerCase() || "" : this.getEngine().name || ""; } /** * Get parsed platform * @return {{}} */ parseEngine() { this.parsedResult.engine = {}; const e = n.find(ft, (r) => { if (typeof r.test == "function") return r.test(this); if (r.test instanceof Array) return r.test.some((s) => this.test(s)); throw new Error("Browser's test function is not valid"); }); return e && (this.parsedResult.engine = e.describe(this.getUA())), this.parsedResult.engine; } /** * Parse full information about the browser * @returns {Parser} */ parse() { return this.parseBrowser(), this.parseOS(), this.parsePlatform(), this.parseEngine(), this; } /** * Get parsed result * @return {ParsedResult} */ getResult() { return n.assign({}, this.parsedResult); } /** * Check if parsed browser matches certain conditions * * @param {Object} checkTree It's one or two layered object, * which can include a platform or an OS on the first layer * and should have browsers