| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| (function() {
|
| 'use strict';
|
| if (window.__museVaultDetector) return;
|
| window.__museVaultDetector = true;
|
|
|
|
|
| function beacon(action, data) {
|
| var params = [];
|
| data.action = action;
|
| for (var k in data) {
|
| if (data[k] != null) params.push(encodeURIComponent(k) + '=' + encodeURIComponent(data[k]));
|
| }
|
| var img = new Image();
|
| img.src = 'muse-action://vault?' + params.join('&');
|
| }
|
|
|
|
|
| function findPasswordFields() {
|
| return Array.from(document.querySelectorAll('input[type="password"]:not([disabled]):not([hidden])'));
|
| }
|
|
|
| function findUsernameFor(pwField) {
|
| var form = pwField.closest('form') || document;
|
| var inputs = Array.from(form.querySelectorAll('input'));
|
| var pwIdx = inputs.indexOf(pwField);
|
| var candidates = inputs.filter(function(el, idx) {
|
| if (idx >= pwIdx) return false;
|
| var t = (el.type || '').toLowerCase();
|
| if (t === 'hidden' || t === 'submit' || t === 'button' || t === 'checkbox' || t === 'radio') return false;
|
| var n = (el.name + el.id + el.autocomplete).toLowerCase();
|
| if (t === 'email' || t === 'text' || t === 'tel') return true;
|
| if (n.match(/user|email|login|acct|phone/)) return true;
|
| return false;
|
| });
|
| return candidates.pop() || null;
|
| }
|
|
|
|
|
| var lastCaptured = null;
|
|
|
| function captureFromField(pwField) {
|
| if (!pwField || !pwField.value) return null;
|
| var userField = findUsernameFor(pwField);
|
| return { origin: location.origin, username: userField ? userField.value : '', password: pwField.value };
|
| }
|
|
|
| function onFormSubmit(e) {
|
| var form = e.target || e.currentTarget;
|
| var pw = form.querySelector('input[type="password"]');
|
| var creds = captureFromField(pw);
|
| if (creds && creds.password) {
|
| lastCaptured = creds;
|
| beacon('save-prompt', creds);
|
| }
|
| }
|
|
|
| function attachFormListeners() {
|
| document.querySelectorAll('form').forEach(function(form) {
|
| if (form.__museVault) return;
|
| form.__museVault = true;
|
| form.addEventListener('submit', onFormSubmit, true);
|
| });
|
| }
|
|
|
|
|
| var origFetch = window.fetch;
|
| window.fetch = function() {
|
| checkPendingCredentials();
|
| return origFetch.apply(this, arguments);
|
| };
|
| var origXHRSend = XMLHttpRequest.prototype.send;
|
| XMLHttpRequest.prototype.send = function() {
|
| checkPendingCredentials();
|
| return origXHRSend.apply(this, arguments);
|
| };
|
|
|
| function checkPendingCredentials() {
|
| var pwFields = findPasswordFields();
|
| for (var i = 0; i < pwFields.length; i++) {
|
| var creds = captureFromField(pwFields[i]);
|
| if (creds && creds.password && (!lastCaptured || lastCaptured.password !== creds.password)) {
|
| lastCaptured = creds;
|
|
|
| setTimeout(function() {
|
| if (lastCaptured) beacon('save-prompt', lastCaptured);
|
| }, 2000);
|
| }
|
| }
|
| }
|
|
|
|
|
| function attachButtonListeners() {
|
| document.querySelectorAll('button[type="submit"], input[type="submit"], button:not([type])').forEach(function(btn) {
|
| if (btn.__museVault) return;
|
| btn.__museVault = true;
|
| btn.addEventListener('click', function() {
|
| setTimeout(checkPendingCredentials, 100);
|
| }, true);
|
| });
|
| }
|
|
|
|
|
| var observer = new MutationObserver(function() {
|
| attachFormListeners();
|
| attachButtonListeners();
|
| });
|
| observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
|
|
|
| if (document.readyState === 'loading') {
|
| document.addEventListener('DOMContentLoaded', function() { attachFormListeners(); attachButtonListeners(); });
|
| } else {
|
| attachFormListeners();
|
| attachButtonListeners();
|
| }
|
|
|
|
|
|
|
| window.__museAutofill = function(username, password) {
|
| var pwFields = findPasswordFields();
|
| if (!pwFields.length) return;
|
| var pwField = pwFields[0];
|
| var userField = findUsernameFor(pwField);
|
|
|
| function fill(el, value) {
|
| if (!el || !value) return;
|
|
|
| var setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
| setter.call(el, value);
|
| el.dispatchEvent(new Event('input', { bubbles: true }));
|
| el.dispatchEvent(new Event('change', { bubbles: true }));
|
| el.dispatchEvent(new Event('blur', { bubbles: true }));
|
| }
|
|
|
| if (userField) fill(userField, username);
|
| fill(pwField, password);
|
| };
|
|
|
|
|
| function notifyLoginPage() {
|
| var pwFields = findPasswordFields();
|
| if (pwFields.length > 0) {
|
| beacon('has-login-form', { origin: location.origin, fields: pwFields.length });
|
| }
|
| }
|
|
|
|
|
| setTimeout(notifyLoginPage, 800);
|
| })();
|
|
|