import Foundation enum RedditLoginBridgeStyle { static let messageHandlerName = "reddoraLogin" /// Intercepts Log In and blocks Reddit's embedded auth modal, which cannot complete /// credential sign-in reliably in WKWebView ("Server error. Try again later."). static let injectionScript = """ (function() { if (window.__reddoraLoginBridgeInstalled) return; window.__reddoraLoginBridgeInstalled = true; const LOGIN_PATTERN = /log\\s*in/i; const HEADER_SELECTORS = 'header, [role="banner"], #header, faceplate-header, nav, shreddit-header-action'; const CLICKABLE_SELECTORS = 'button, a, [role="button"], faceplate-tracker, faceplate-button'; const AUTH_MODAL_SELECTORS = [ 'auth-flow-modal', '[data-testid="login-modal"]', '#credentials-login-popup', 'shreddit-signup-drawer', 'faceplate-modal[slot="login"]', 'faceplate-modal[open]' ].join(', '); const boundLoginTriggers = new WeakSet(); function nodeLabel(node) { if (!(node instanceof Element)) return ''; return ( (node.textContent || '') + ' ' + (node.getAttribute('aria-label') || '') + ' ' + (node.getAttribute('title') || '') + ' ' + (node.getAttribute('href') || '') ).trim(); } function isLoginPage() { const host = window.location.hostname.toLowerCase(); return (host === 'www.reddit.com' || host === 'reddit.com' || host === 'new.reddit.com') && /^\\/login\\/?$/i.test(window.location.pathname); } function requestInAppLogin() { if (isLoginPage()) return; if (window.__reddoraLoginPanelRequested) return; window.__reddoraLoginPanelRequested = true; setTimeout(function() { window.__reddoraLoginPanelRequested = false; }, 1000); if ( window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.reddoraLogin ) { window.webkit.messageHandlers.reddoraLogin.postMessage('open'); } } function interceptLoginEvent(event) { if (isLoginPage()) return false; if (eventMatchesLoginTrigger(event)) { event.preventDefault(); event.stopImmediatePropagation(); window.__reddoraCloseRedditAuthModal && window.__reddoraCloseRedditAuthModal(); requestInAppLogin(); return true; } const loginButton = document.getElementById('login-button'); if (loginButton && eventMatchesButtonBounds(event, loginButton)) { event.preventDefault(); event.stopImmediatePropagation(); window.__reddoraCloseRedditAuthModal && window.__reddoraCloseRedditAuthModal(); requestInAppLogin(); return true; } return false; } function eventMatchesButtonBounds(event, button) { if (!(button instanceof Element)) return false; if (!(event instanceof MouseEvent) && event.type !== 'pointerdown' && event.type !== 'mousedown') { return false; } if (button.contains(event.target)) return false; const rect = button.getBoundingClientRect(); const x = event.clientX; const y = event.clientY; return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom; } function eventMatchesLoginTrigger(event) { const path = event.composedPath ? event.composedPath() : [event.target]; for (let i = 0; i < path.length; i++) { if (resolveLoginTrigger(path[i])) return true; } return false; } function resolveLoginTrigger(node) { if (!(node instanceof Element)) return null; if (node.id === 'login-button') return node; if (node.getAttribute('data-testid') === 'login-button') return node; const clickable = node.matches(CLICKABLE_SELECTORS) ? node : node.closest(CLICKABLE_SELECTORS); if (!clickable) return null; if (clickable.id === 'login-button') return clickable; if (clickable.getAttribute('data-testid') === 'login-button') return clickable; const href = (clickable.getAttribute('href') || '').toLowerCase(); if (href.includes('/login')) return clickable; const label = nodeLabel(clickable); if (!LOGIN_PATTERN.test(label)) return null; if (clickable.closest(HEADER_SELECTORS)) return clickable; const rect = clickable.getBoundingClientRect(); if (rect.top >= 0 && rect.top < 96 && rect.width > 0 && rect.height > 0) { return clickable; } return null; } function isLoginTriggerElement(node) { return resolveLoginTrigger(node) !== null; } function isAuthModal(element) { if (!(element instanceof Element)) return false; if (element.matches( 'auth-flow-modal, [data-testid="login-modal"], #credentials-login-popup, shreddit-signup-drawer' )) { return true; } if (element.matches('shreddit-overlay-display')) { return !!element.querySelector( 'shreddit-signup-drawer, auth-flow-modal, input[type="password"]' ); } if (element.matches('faceplate-modal')) { const label = nodeLabel(element); return LOGIN_PATTERN.test(label) || !!element.querySelector('input[type="password"]'); } return false; } window.__reddoraCloseRedditAuthModal = function() { document.querySelectorAll(AUTH_MODAL_SELECTORS).forEach(function(element) { if (!isAuthModal(element)) return; const closeButton = element.querySelector( '[aria-label="Close"], [aria-label="close"], button[close]' ); if (closeButton) { closeButton.click(); } else { element.remove(); } }); }; function bindDirectLoginListeners(root) { function walk(node) { if (!(node instanceof Element)) return; const trigger = resolveLoginTrigger(node); if (trigger && !boundLoginTriggers.has(trigger)) { boundLoginTriggers.add(trigger); ['pointerdown', 'mousedown', 'click'].forEach(function(type) { trigger.addEventListener(type, interceptLoginEvent, true); }); } if (node.shadowRoot) walk(node.shadowRoot); const children = node.children; for (let i = 0; i < children.length; i++) { walk(children[i]); } } walk(root || document.documentElement); } function installAuthModalBlocker() { if (isLoginPage()) return; const STYLE_ID = 'reddora-auth-block-style'; if (!document.getElementById(STYLE_ID)) { const style = document.createElement('style'); style.id = STYLE_ID; style.textContent = [ 'auth-flow-modal,', '[data-testid="login-modal"],', '#credentials-login-popup,', 'shreddit-signup-drawer {', ' display: none !important;', ' pointer-events: none !important;', '}' ].join('\\n'); (document.head || document.documentElement).appendChild(style); } const observer = new MutationObserver(function() { if (isLoginPage()) return; bindDirectLoginListeners(document.documentElement); let foundAuthModal = false; document.querySelectorAll(AUTH_MODAL_SELECTORS).forEach(function(element) { if (!isAuthModal(element)) return; foundAuthModal = true; element.remove(); }); if (foundAuthModal) { requestInAppLogin(); } }); observer.observe(document.documentElement, { childList: true, subtree: true }); } ['pointerdown', 'mousedown', 'click'].forEach(function(type) { document.addEventListener(type, interceptLoginEvent, true); }); bindDirectLoginListeners(document.documentElement); installAuthModalBlocker(); [250, 750, 1500, 3000].forEach(function(delay) { setTimeout(function() { bindDirectLoginListeners(document.documentElement); }, delay); }); })(); """ static let closeAuthModalScript = "window.__reddoraCloseRedditAuthModal && window.__reddoraCloseRedditAuthModal();" }