|
|
@@ -0,0 +1,151 @@
|
|
|
+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';
|
|
|
+ const AUTH_MODAL_SELECTORS = [
|
|
|
+ 'auth-flow-modal',
|
|
|
+ '[data-testid="login-modal"]',
|
|
|
+ '#credentials-login-popup',
|
|
|
+ 'faceplate-modal[slot="login"]',
|
|
|
+ 'faceplate-modal[open]'
|
|
|
+ ].join(', ');
|
|
|
+
|
|
|
+ function nodeLabel(node) {
|
|
|
+ if (!(node instanceof Element)) return '';
|
|
|
+ return (
|
|
|
+ (node.textContent || '') + ' ' +
|
|
|
+ (node.getAttribute('aria-label') || '') + ' ' +
|
|
|
+ (node.getAttribute('title') || '')
|
|
|
+ ).trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ function isHeaderLoginTrigger(node) {
|
|
|
+ if (!(node instanceof Element)) return false;
|
|
|
+
|
|
|
+ const clickable = node.matches('button, a, [role="button"], faceplate-tracker, faceplate-button')
|
|
|
+ ? node
|
|
|
+ : node.closest('button, a, [role="button"], faceplate-tracker, faceplate-button');
|
|
|
+ if (!clickable) return false;
|
|
|
+
|
|
|
+ if (clickable.id === 'login-button') return true;
|
|
|
+ if (clickable.getAttribute('data-testid') === 'login-button') return true;
|
|
|
+
|
|
|
+ const label = nodeLabel(clickable);
|
|
|
+ if (!LOGIN_PATTERN.test(label)) return false;
|
|
|
+
|
|
|
+ return !!clickable.closest(HEADER_SELECTORS);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 isAuthModal(element) {
|
|
|
+ if (!(element instanceof Element)) return false;
|
|
|
+ if (element.matches('auth-flow-modal, [data-testid="login-modal"], #credentials-login-popup')) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ 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 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 {',
|
|
|
+ ' display: none !important;',
|
|
|
+ ' pointer-events: none !important;',
|
|
|
+ '}'
|
|
|
+ ].join('\\n');
|
|
|
+ (document.head || document.documentElement).appendChild(style);
|
|
|
+ }
|
|
|
+
|
|
|
+ const observer = new MutationObserver(function() {
|
|
|
+ if (isLoginPage()) return;
|
|
|
+ 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 });
|
|
|
+ }
|
|
|
+
|
|
|
+ document.addEventListener('click', function(event) {
|
|
|
+ if (isLoginPage()) return;
|
|
|
+ const path = event.composedPath();
|
|
|
+ for (let i = 0; i < path.length; i++) {
|
|
|
+ if (!isHeaderLoginTrigger(path[i])) continue;
|
|
|
+
|
|
|
+ event.preventDefault();
|
|
|
+ event.stopImmediatePropagation();
|
|
|
+ window.__reddoraCloseRedditAuthModal();
|
|
|
+ requestInAppLogin();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }, true);
|
|
|
+
|
|
|
+ installAuthModalBlocker();
|
|
|
+ })();
|
|
|
+ """
|
|
|
+
|
|
|
+ static let closeAuthModalScript = "window.__reddoraCloseRedditAuthModal && window.__reddoraCloseRedditAuthModal();"
|
|
|
+}
|