<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<meta http-equiv="X-UA-Compatible" content="IE=edge" />
	<link rel="icon" id="ui3launcher1">
	<link rel="apple-touch-icon" id="ui3launcher2">
	<meta name="viewport" content="width=device-width, initial-scale=1" />
	<meta name="apple-mobile-web-app-capable" content="yes"> <!--iOS Safari-->
	<meta name="apple-mobile-web-app-status-bar-style" content="black">
	<meta name="mobile-web-app-capable" content="yes"><!--Android Chrome-->
	<meta name="theme-color" content="#000000">
	<link rel="manifest" id="ui3manifest">
	<title>Blue Iris Login</title>
	<script type="text/javascript">
		var login_version = "33";
		var bi_version = "5.9.9.98";
		var combined_version = login_version + "-" + bi_version;
		var appPath = "/" + "/".replace(/^\/+|\/+$/g, '');
		if (!appPath.endsWith("/"))
			appPath = appPath + "/";
	</script>
	<script type="text/javascript">
		// Each method in suppressErrorCallbacks can inspect the error and return true to suppress reporting of the error.
		var suppressErrorCallbacks = [
			function (err) // [2020-11-11] LG TV browser injects bad script on line 1 of htm file.
			{
				return err.line === 1 && err.msg && err.msg.indexOf("'getAttribute'") > -1 && err.url && err.url.indexOf(".htm") > -1;
			},
			function (err) // [2021-07-19] LG TV browser fails repeatedly.
			{
				return err.line === 0 && err.charIdx === 0 && err.msg && err.msg.indexOf("Script error.") > -1 && (!err.url || err.url == "");
			},
			function (err) // ublock origin in Brave browser
			{
				return err.msg && err.msg == "Uncaught TypeError: Cannot redefine property: getElementById" && err.url === "";
			},
			function (err) // Edge iOS
			{
				return err.msg && err.msg.indexOf("instantSearchSDKJSBridgeClearHighlight") > -1;
			},
			function (err)
			{
				return err.msg && err.msg.indexOf("EvalError") > -1 && err.msg.indexOf("debug-evaluate") > -1;
			},
			function (err, ex)
			{
				return err.msg && err.msg.indexOf("getBoundingClientRect") > -1 && ex && ex.stack.indexOf("at testUserInput ") > -1; // AutoplayStopper extension
			},
			function (err, ex)
			{
				return isUrlPathBad(err.url); // Unknown cause
			},
			function (err, ex)
			{
				return err.msg && err.msg.indexOf("'window.ethereum.") > -1;
			},
		];

		window.onerror = function (msg, url, line, charIdx, err)
		{
			try
			{
				var uiVersionStr = "unknown";
				if (typeof login_version !== "undefined")
					uiVersionStr = login_version;
				var biVersionStr = "unknown";
				if (typeof bi_version != "undefined")
					biVersionStr = bi_version;
				if (!url)
					url = "";
				url = url.replace(/\/\/.*?\//, '//censored_hostname/');
				var errDetails = msg + "\nat " + url + " [" + line + ":" + charIdx + "]";
				try
				{
					var betterDetails = ErrorToText(err);
					if (betterDetails)
						errDetails += "\n" + betterDetails.replace(new RegExp(location.origin, "g"), "");
				}
				catch (ex) { }
				var errHeading = "An unexpected error has occurred in Blue Iris Login (v " + uiVersionStr + " / " + biVersionStr + ").";
				var errStr = errHeading + " A full refresh may solve the problem (CTRL + F5). If you wish to report the error, please SCREENSHOT the browser now.\n\n" + errDetails + "\n" + navigator.userAgent;
				errStr = errStr.replace(/\n/g, ' \n');

				var errObj = { msg: msg, url: url, line: line, charIdx: charIdx, uiVersionStr: uiVersionStr, biVersionStr: biVersionStr };
				for (var i = 0; i < suppressErrorCallbacks.length; i++)
				{
					try
					{
						if (suppressErrorCallbacks[i](errObj, err))
						{
							try { console.log("The following error report was suppressed by callback " + i + ":"); console.error(errStr); }
							catch (ex) { }
							return;
						}
					}
					catch (ex)
					{
						try { console.log("Error suppression callback " + i + " had an error:"); console.error(errStr); }
						catch (ex) { }
						return;
					}
				}

				prompt(errStr, ("Please COPY this text to share with support.  Error in Blue Iris Login (v " + uiVersionStr + " / " + biVersionStr + ")\n\n" + errDetails + "\n\n" + navigator.userAgent).replace(/\n/g, ' \n'));
			}
			catch (ex)
			{
				alert(ex);
			}
		};
		function ErrorToText(err)
		{
			if (err && err.stack)
			{
				var msg = err.stack;
				if (typeof err.message === "string" && msg.indexOf(err.message) < 0)
					msg = err.message + "\n" + msg;
				if (typeof err.name === "string" && msg.indexOf(err.name) < 0)
					msg = err.name + ": " + msg;
				return msg;
			}
			return null;
		}
		function isUrlPathBad(b) { const c = /^[A-Za-z0-9=_-]+$/; try { const a = (new URL(b)).pathname.slice(1); return 20 < a.length && c.test(a) } catch (a) { return !1 } };
		function SetColorTheme()
		{
			var theme;
			if (bi_version && bi_version.indexOf("6.") === 0)
				theme = "blue-iris-6";
			else
				theme = "blue-iris-5";
			theme = "theme-" + theme;
			document.documentElement.classList.add(theme);
		}
		try { SetColorTheme(); } catch (e) { console.error(e); }
	</script>
	<script type="text/javascript">
		document.getElementById("ui3manifest").href = 'applet/manifest.json?v=' + combined_version;
		document.getElementById("ui3launcher1").href = 'applet/logos/launcher-icon48.png?v=' + combined_version;
		document.getElementById("ui3launcher2").href = 'applet/logos/launcher-icon256_ios.webp?v=' + combined_version;
	</script>
	<style type="text/css">
		body
		{
			font-family: sans-serif;
			background: #212325;
		}

		#loginLoading
		{
			display: none;
			text-shadow: 0 0 10px rgba(0,0,0,0.3);
			position: absolute;
			text-align: center;
			top: 40%;
			width: 100%;
			color: #FFFFFF;
		}

			#loginLoading h1
			{
				margin: 0 0 20px 0;
				font-size: 32px;
			}

			#loginLoading div
			{
				font-size: 20px;
			}

		#login
		{
			display: none;
		}

		.checkboxWrapper
		{
			max-width: 488px;
		}
	</style>
</head>
<body>
	<div id="loginLoading">
		<h1>KLW Red-Queen</h1>
		<div>Loading login page...</div>
	</div>
	<div id="login">
		<h1>KLW Red-Queen</h1>
		<input id="txtUn" type="text" class="text" placeholder="Username" autocapitalize="off" autocorrect="off" />
		<input id="txtPw" type="password" class="text" placeholder="Password" onkeypress="return pwKeypress(this, event);" autocapitalize="off" autocorrect="off" />
		<div class="checkboxWrapper">
			<input id="cbLoginAutomatically" type="checkbox" class="checkbox" onchange="cbLoginAutomaticallyClicked();" /><label for="cbLoginAutomatically" id="lblLoginAutomatically"><span class="ui"></span>Log in automatically:</label>
		</div>
		<div id="status_wrapper_upper"></div>
		<input id="btnLogin" type="button" class="btn" value="Log in" onclick="login();" />
		<div id="status_wrapper_lower"><div id="status"></div></div>
	</div>
	<script type="text/javascript">
		var loadingOpacity = 0;
		function IncreaseLoadingOpacity()
		{
			loadingOpacity += 0.05;
			if (loadingOpacity > 1)
				loadingOpacity = 1;
			var ele = document.getElementById('loginLoading');
			ele.style.display = "block";
			ele.style.opacity = loadingOpacity;
			if (loadingOpacity < 1)
				showLoadingMessageTimeout = setTimeout(IncreaseLoadingOpacity, 33);
		}
		var showLoadingMessageTimeout = setTimeout(IncreaseLoadingOpacity, 67);

		document.write('<link href="applet/loginStyles.css?v=' + combined_version + '" rel="stylesheet" />'
			+ '<script src="applet/loginScripts.js?v=' + combined_version + '"><\/script>');
	</script>
	<script type="text/javascript">
		/* eslint no-extra-parens: 0 */
		var autologin_timeout_1 = null;
		var autologin_timeout_2 = null;
		var existingSession = "1e053a39181e630111d935f9739c71ad";
		var authStatus = "unknown";
		var authExempt = "no";
		var loginSession = "";
		var isStoredDataLoaded = false;
		var windowUnloading = false;
		var cookiesEnabled;
		var localStorageEnabled;

		/**
		 * Changes the current URL by removing the specified query string parameter(s) from it.
		 * @returns {String} Returns null if successful, otherwise returns the new URL if changing the history state failed.
		 */
		function NavRemoveUrlParams()
		{
			var url = RemoveUrlParams.apply(this, arguments);
			try { history.replaceState(history.state, "", url); return null; } catch (ex) { return url; }
		}
		function RemoveUrlParams()
		{
			var s = location.search;
			for (var i = 0; i < arguments.length; i++)
			{
				var param = arguments[i];
				var rx = new RegExp('(&|\\?)' + param + '=[^&?#%]+', 'gi');
				s = s.replace(rx, "");
				while (s.indexOf("&") === 0)
				{
					if (s.length > 1)
						s = s.substr(1);
					else
						s = "";
				}
				if (s.length > 0 && s.indexOf("?") === -1)
					s = "?" + s;
			}
			return location.origin + location.pathname + s + location.hash;
		}

		NavRemoveUrlParams("session");

		$(function ()
		{
			cookiesEnabled = testCookieFunctionality();
			localStorageEnabled = isLocalStorageEnabled();
			var skipAutoLogin = GetPersistedValue("bi_override_disable_auto_login_once") === "1";
			if (UrlParameters.Get("autologin") === "0")
			{
				skipAutoLogin = true;
				var url = NavRemoveUrlParams("session", "autologin");
				if (url)
				{
					SetPersistedValue("bi_override_disable_auto_login_once", "1");
					location.href = url;
					return;
				}
			}
			if (skipAutoLogin)
			{
				SetPersistedValue("bi_override_disable_auto_login_once", "0");
				console.log('Auto login was temporarily disabled because "autologin=0" URL parameter was observed (the UI3 "Main Menu" > "Log Out" button sets this parameter).');
			}
			var lastUnload = GetPersistedValue("bi_lastunload");
			if (lastUnload > Date.now())
				SetPersistedValue("bi_lastunload", 0);
			else if (!skipAutoLogin)
			{
				skipAutoLogin = Date.now() - lastUnload < 5000;
				if (skipAutoLogin)
					console.log('Auto login was temporarily disabled because UI3 was determined to be running within the previous 5 seconds.');
			}
			clearTimeout(showLoadingMessageTimeout);
			$("#loginLoading").hide();
			$("#login").show();
			if (typeof window.JSON === 'undefined')
			{
				$("#login").html("<div>Your web browser is too old to use the Blue Iris web interface properly.<br><br>To proceed with this browser, disable the \"Secure only\" requirement within Blue Iris's web server settings.</div>");
				$("#login").css("color", "#EEEEEE").css("margin", "8px");
				return;
			}
			if (!existingSession || (existingSession.length === 11 && existingSession.startsWith("%") && existingSession.endsWith("%") && existingSession.indexOf("SESSION") === 2))
			{
				$("#login").html("<div>Session data was not provided as expected. This login page only works when served by Blue Iris 4.8.2.3 or newer.</div>");
				$("#login").css("color", "#EEEEEE").css("margin", "8px");
				return;
			}
			if (!localStorageEnabled)
				$("#cbLoginAutomatically").parent().text("Note: Local Storage is disabled in your browser, so credentials can not be saved.").css("color", "#EEEEEE");
			SetStatus();
			$(window).resize(resized);
			resized();
			window.onbeforeunload = function ()
			{
				windowUnloading = true;
				cbLoginAutomaticallyClicked();
				return;
			};
			// Handle automatic login
			if (GetPersistedValue("bi_rememberMe") === "1")
			{
				$("#cbLoginAutomatically").attr('checked', 'checked');
				$("#txtUn").val(Base64.decode(GetPersistedValue("bi_username")));
				$("#txtPw").val(Base64.decode(GetPersistedValue("bi_password")));

				if (!skipAutoLogin)
				{
					if ($("#txtUn").val() !== "" && $("#txtPw").val() !== "")
					{
						login();
					}
				}
			}
			else
			{
				$("#cbLoginAutomatically").removeAttr('checked');
				SetPersistedValue("bi_username", "");
				SetPersistedValue("bi_password", "");
			}

			var sessionType = "";

			// authExempt predefined values: no, user, admin, unknown (should not happen)
			if (authExempt === "user")
				sessionType = "anonymous user";
			else if (authExempt === "admin")
				sessionType = "anonymous admin";

			// authStatus predefined values: unknown, admin, anonymous, user
			if (authStatus === "admin")
				sessionType = "existing admin";
			else if (authStatus === "user")
				sessionType = "existing user";
			else if (authStatus === "anonymous")
				sessionType = "existing anonymous user";

			if (sessionType)
				SetStatus("An " + sessionType + ' session is available. <a href="javascript:UseAltSession(\'' + sessionType + '\')">Click here to use it.</a>');

			// Set focus on first empty field
			if (!$("#txtUn").val())
				$("#txtUn").get(0).focus();
			else if (!$("#txtPw").val())
				$("#txtPw").get(0).focus();
			else
				$("#btnLogin").get(0).focus();

			isStoredDataLoaded = true;
		});
		function login()
		{
			cbLoginAutomaticallyClicked();
			$("#btnLogin").val("Logging in ...");
			SetStatus();
			if (authStatus === "unknown")
				loginSession = existingSession;

			if (!loginSession)
				authenticateNewSession();
			else
				authenticateSession(loginSession);
		}
		function authenticateNewSession(user, pass)
		{
			ExecJSON({ cmd: "login" }, function (response)
			{
				loginSession = response.session;
				authenticateSession(loginSession, true);
			}, loginFail);
		}
		function authenticateSession(session, isNewSession)
		{
			var myResponse = md5($("#txtUn").val() + ":" + session + ":" + $("#txtPw").val());
			ExecJSON({ cmd: "login", session: session, response: myResponse }, function (response)
			{
				if (response.result === "success")
				{
					$("#btnLogin").attr("disabled", "disabled").val("Redirecting...");
					existingSession = response.session;
					LeaveLoginPage();
				}
				else
				{
					var isExpired;
					if (compareVersions(bi_version, "5.8.1.1") >= 0)
					{
						isExpired = response.result === "fail"
							&& response.data
							&& typeof response.data.reason === "string"
							&& response.data.reason.toUpperCase() === "INVALID SESSION";
					}
					else
					{
						// BI prior to 5.8.1.1 did not return a data.reason string in the case of invalid session.
						isExpired = response.result === "fail" && (!response.data || !response.data.reason);
					}
					if (isExpired && !isNewSession)
					{
						// This happens when the session we were trying to authenticate has expired.
						authenticateNewSession();
					}
					else
					{
						$("#cbLoginAutomatically").removeAttr('checked');
						SetPersistedValue("bi_rememberMe", "0");
						SetPersistedValue("bi_username", "");
						SetPersistedValue("bi_password", "");
						$("#btnLogin").val("Log in");
						HandleError(response.data && response.data.reason ? response.data.reason : "Login failed but Blue Iris did not provide a reason.");
					}
				}
			}, loginFail);
		}
		function loginFail(jqXHR, textStatus, errorThrown)
		{
			HandleError("Unable to contact Blue Iris server");
			$("#btnLogin").val("Log in");
		}
		function UseAltSession(sessionType)
		{
			if (!sessionType)
				alert("Error. Please reload.");
			else if (sessionType.indexOf("existing") > -1)
				LeaveLoginPage();
			else
			{
				existingSession = "";
				LeaveLoginPage();
			}
		}
		function LeaveLoginPage()
		{
			var page = UrlParameters.Get("page");
			if (page.match(/timeout\.htm/i))
				page = page.replace(/timeout\.htm.*/i, 'ui3.htm');
			if (page.indexOf(appPath) !== 0)
				page = appPath + page.replace(/^\/+/g, '');
			page = page.replace(/[&?]session=[^&?#%]+/gi, '');
			if (existingSession)
			{
				page += (page.indexOf("?") < 0 ? "?" : "&") + "session=" + existingSession;
				if (cookiesEnabled)
					$.cookie("session", existingSession, { path: "/" });
			}
			location.href = page + location.hash;
		}
		function cancelAutoLogin()
		{
			if (autologin_timeout_1 !== null)
			{
				clearTimeout(autologin_timeout_1);
				autologin_timeout_1 = null;
			}
			if (autologin_timeout_2 !== null)
			{
				clearTimeout(autologin_timeout_2);
				autologin_timeout_2 = null;
			}
			$("#btnLogin").val(windowUnloading ? "Redirecting..." : "Log in");
		}
		function cbLoginAutomaticallyClicked()
		{
			cancelAutoLogin();
			if (!isStoredDataLoaded)
				return;
			var isChecked = $("#cbLoginAutomatically").is(":checked");
			SetPersistedValue("bi_rememberMe", isChecked ? "1" : "0");
			SetPersistedValue("bi_username", isChecked ? Base64.encode($("#txtUn").val()) : "");
			SetPersistedValue("bi_password", isChecked ? Base64.encode($("#txtPw").val()) : "");
		}
		function GetPersistedValue(key)
		{
			var value;
			if (localStorageEnabled)
				value = localStorage.getItem(key);
			if (!value)
				value = "";
			return value;
		}
		function SetPersistedValue(key, value)
		{
			if (localStorageEnabled)
				return localStorage.setItem(key, value);
		}
		function pwKeypress(ele, e)
		{
			var keycode;
			if (window.event) keycode = window.event.keyCode;
			else if (typeof e !== "undefined" && e) keycode = e.which;
			else return true;

			if (keycode === 13)
			{
				login();
				return false;
			}
			else
				return true;
		}
		function resized()
		{
			if ($("#status").is(":visible"))
			{
				$("#status").hide();
				$("#status").css("max-width", $("#login").width() + "px");
				$("#status").show();
			}
			$('#login').css({ position: 'absolute', left: ($(window).width() - $('#login').outerWidth()) / 2, top: ($(window).height() - $('#login').outerHeight()) / 2 });
			$("#status").css("max-width", $("#login").width() + "px");
			var heightTotal = 0;
			$("#login").children().each(function (idx, ele)
			{
				heightTotal += $(ele).outerHeight(true);
			});
			if (heightTotal > $(window).height())
			{
				if ($("#status").parent().attr("id") !== "status_wrapper_upper")
					$("#status_wrapper_upper").append($("#status"));
			}
			else
			{
				if ($("#status").parent().attr("id") !== "status_wrapper_lower")
					$("#status_wrapper_lower").append($("#status"));
			}
			$("#lblLoginAutomatically").parent().css("padding-left", (($('#login').outerWidth() - $("#lblLoginAutomatically").outerWidth(true)) / 2) + "px");
		}
		function HandleError(error)
		{
			SetStatus(error, "#FF6262");
		}
		function SetStatus(html, color)
		{
			if (typeof html === "undefined" || html === null || html === "")
			{
				html = "";
				$("#status").hide();
			}
			else
				$("#status").show();
			if (typeof color === "undefined" || color === null || color === "")
				color = "#FFFFFF";
			$("#status").html(html);
			$("#status").css("color", color);
			resized();
		}
	</script>
</body>
</html>
