getAutoFillScript static method

String getAutoFillScript(
  1. String aadhaarNumber,
  2. List<String?>? autoSelectCheckBox
)

Generate comprehensive auto-fill script for DigiLocker

Implementation

static String getAutoFillScript(String aadhaarNumber, List<String?>? autoSelectCheckBox) {
  // Convert the list to JavaScript array format
  final checkboxList =
  autoSelectCheckBox != null && autoSelectCheckBox.isNotEmpty
      ? autoSelectCheckBox
      .where((item) => item != null)
      .map((item) => '"$item"')
      .join(',')
      : '';

  return '''
(function() {
  if (!window.FlutterChannel) {
      window.FlutterChannel = { postMessage: function(msg) { console.log('FlutterChannel:', msg); } };
  }

  var AADHAAR_NUMBER = ${aadhaarNumber.isNotEmpty ? '"$aadhaarNumber"' : '""'};
  var AUTO_SELECT_CHECKBOXES = [$checkboxList];

  if (typeof window.digilockerAlreadyFilled === 'undefined') {
      window.digilockerAlreadyFilled = false;
      window.consentCheckboxSelected = false;
      window.additionalCheckboxesSelected = {};
  }

  // Helper: Check if field is OTP/PIN/Password
  function isOTPOrPINField(input) {
      if (!input) return false;
      var id = (input.id || '').toLowerCase();
      var name = (input.name || '').toLowerCase();
      var placeholder = (input.placeholder || '').toLowerCase();
      var type = (input.type || '').toLowerCase();
      var className = (input.className || '').toLowerCase();
      var indicators = ['otp', 'pin', 'password', 'pwd', 'mpin', 'verification'];
      for (var i = 0; i < indicators.length; i++) {
          if (id.includes(indicators[i]) || name.includes(indicators[i]) ||
              placeholder.includes(indicators[i]) || className.includes(indicators[i])) {
              return true;
          }
      }
      return type === 'password';
  }

  // Helper: Check if field is for Aadhaar
  function isAadhaarField(input) {
      var text = ((input.id || '') + (input.name || '') +
                 (input.placeholder || '') + (input.className || '')).toLowerCase();
      return text.includes('aadhaar') || text.includes('aadhar') || text.includes('uid');
  }

  // Helper: Find best input field
  function findBestInput() {
      var inputs = document.querySelectorAll('input[type="text"], input[type="number"], input[type="tel"], input:not([type])');

      // First pass: Look for Aadhaar-specific fields
      for (var i = 0; i < inputs.length; i++) {
          var input = inputs[i];
          if (input.offsetParent === null) continue;
          if (isOTPOrPINField(input)) continue;
          if (input.value && input.value.length > 3) continue;
          if (isAadhaarField(input)) return input;
      }

      // Second pass: Any visible empty field
      for (var i = 0; i < inputs.length; i++) {
          var input = inputs[i];
          if (input.offsetParent === null) continue;
          if (isOTPOrPINField(input)) continue;
          if (!input.value) return input;
      }

      return null;
  }

  // Helper: Fill input with proper events
  function fillInput(input, value) {
      try {
          input.focus();
          var setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
          setter.call(input, value);

          // Dispatch events
          input.dispatchEvent(new Event('input', { bubbles: true }));
          input.dispatchEvent(new Event('change', { bubbles: true }));
          input.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true }));

          return true;
      } catch (e) {
          FlutterChannel.postMessage('debug:Fill error - ' + e.message);
          return false;
      }
  }

  function isTextEntryElement(element) {
      if (!element || !element.tagName) return false;

      var tagName = element.tagName.toLowerCase();
      if (tagName === 'textarea') return true;
      if (element.isContentEditable) return true;
      if (tagName !== 'input') return false;

      var type = (element.type || '').toLowerCase();
      return ['text', 'number', 'tel', 'password', 'email', 'search', 'url', ''].indexOf(type) !== -1;
  }

  function getViewportHeight() {
      if (window.visualViewport && window.visualViewport.height) {
          return window.visualViewport.height;
      }

      return window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0;
  }

  function scheduleEnsureActiveFieldVisible(delay) {
      if (window.digilockerEnsureFieldTimer) {
          clearTimeout(window.digilockerEnsureFieldTimer);
      }

      window.digilockerEnsureFieldTimer = setTimeout(function() {
          ensureActiveFieldVisible();
      }, delay || 0);
  }

  function ensureActiveFieldVisible() {
      var activeElement = document.activeElement;
      if (!isTextEntryElement(activeElement)) return;
      var rect = activeElement.getBoundingClientRect();
      var viewportHeight = getViewportHeight();
      if (!viewportHeight) return;

      var topPadding = 24;
      var bottomPadding = 32;
      var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
      var visibleTop = topPadding;
      var visibleBottom = viewportHeight - bottomPadding;

      if (rect.top >= visibleTop && rect.bottom <= visibleBottom) {
          return;
      }

      var targetTop = scrollTop + rect.top - Math.max((viewportHeight - rect.height) / 2, topPadding);

      if (Math.abs(targetTop - scrollTop) < 12) {
          return;
      }

      window.scrollTo(0, Math.max(targetTop, 0));
  }

  function installKeyboardHandlers() {
      if (window.digilockerKeyboardHandlersInstalled) return;
      window.digilockerKeyboardHandlersInstalled = true;

      document.addEventListener('focusin', function(event) {
          if (!isTextEntryElement(event.target)) return;

          scheduleEnsureActiveFieldVisible(80);
          scheduleEnsureActiveFieldVisible(220);
      }, true);

      window.addEventListener('resize', function() {
          scheduleEnsureActiveFieldVisible(120);
      });

      if (window.visualViewport) {
          window.visualViewport.addEventListener('resize', function() {
              scheduleEnsureActiveFieldVisible(120);
          });
      }
  }

  // Fill split Aadhaar fields (4-4-4 format for Sign-Up)
  function fillSplitAadhaarFields() {
      var f1 = document.getElementById('aadhaar_1');
      var f2 = document.getElementById('aadhaar_2');
      var f3 = document.getElementById('aadhaar_3');

      if (!f1 || !f2 || !f3) return false;
      if (!AADHAAR_NUMBER || AADHAAR_NUMBER.length !== 12) return false;

      fillInput(f1, AADHAAR_NUMBER.substring(0, 4));
      setTimeout(function() {
          fillInput(f2, AADHAAR_NUMBER.substring(4, 8));
      }, 120);
      setTimeout(function() {
          fillInput(f3, AADHAAR_NUMBER.substring(8, 12));
          window.digilockerAlreadyFilled = true;
          FlutterChannel.postMessage('prefillStatus:AADHAAR_SPLIT_FILLED');
      }, 240);

      return true;
  }

  // Main auto-fill function
  function attemptAadhaarAutofill() {
      if (window.digilockerAlreadyFilled) return;
      if (!AADHAAR_NUMBER || AADHAAR_NUMBER.length < 10) return;

      // Try split fields first (Sign-Up flow)
      if (document.getElementById('aadhaar_1')) {
          if (fillSplitAadhaarFields()) return;
      }

      // Try single field (Sign-In flow)
      var input = findBestInput();
      if (input && fillInput(input, AADHAAR_NUMBER)) {
          window.digilockerAlreadyFilled = true;
          FlutterChannel.postMessage('prefillStatus:AADHAAR_FILLED');
      }
  }

  // Auto-select consent checkbox for Aadhaar card
  function selectAadhaarConsentCheckbox() {
      if (window.consentCheckboxSelected) return;

      var labels = document.querySelectorAll('label.form-check-label');
      var targetRow = null;

      for (var i = 0; i < labels.length; i++) {
          var text = (labels[i].innerText || '').toLowerCase();
          if (text.includes('aadhaar') && text.includes('card')) {
              targetRow = labels[i].closest('.d-flex');
              break;
          }
      }

      if (!targetRow) return;

      var checkbox = targetRow.querySelector('input[type="checkbox"][name="consent"]');
      if (!checkbox) return;

      checkbox.checked = false;
      checkbox.dispatchEvent(new Event('change', { bubbles: true }));

      setTimeout(function() {
          checkbox.checked = true;
          checkbox.dispatchEvent(new Event('input', { bubbles: true }));
          checkbox.dispatchEvent(new Event('change', { bubbles: true }));
          checkbox.dispatchEvent(new Event('click', { bubbles: true }));
          window.consentCheckboxSelected = true;
          FlutterChannel.postMessage('checkboxSelected:AADHAAR_CARD');
      }, 100);
  }

  // Auto-select additional checkboxes based on AUTO_SELECT_CHECKBOXES list
  function selectAdditionalCheckboxes() {
      if (!AUTO_SELECT_CHECKBOXES || AUTO_SELECT_CHECKBOXES.length === 0) return;

      var labels = document.querySelectorAll('label.form-check-label');

      for (var i = 0; i < AUTO_SELECT_CHECKBOXES.length; i++) {
          var targetText = AUTO_SELECT_CHECKBOXES[i].toLowerCase();

          // Skip if already selected
          if (window.additionalCheckboxesSelected[targetText]) continue;

          for (var j = 0; j < labels.length; j++) {
              var labelText = (labels[j].innerText || '').toLowerCase();

              // Check if label text matches the target text
              if (labelText.includes(targetText) || targetText.includes(labelText.trim())) {
                  var targetRow = labels[j].closest('.d-flex');
                  if (!targetRow) continue;

                  var checkbox = targetRow.querySelector('input[type="checkbox"][name="consent"]');
                  if (!checkbox) continue;

                  // Mark as selected before attempting to avoid duplicates
                  window.additionalCheckboxesSelected[targetText] = true;

                  // Toggle checkbox to ensure it's checked
                  checkbox.checked = false;
                  checkbox.dispatchEvent(new Event('change', { bubbles: true }));

                  (function(cb, txt) {
                      setTimeout(function() {
                          cb.checked = true;
                          cb.dispatchEvent(new Event('input', { bubbles: true }));
                          cb.dispatchEvent(new Event('change', { bubbles: true }));
                          cb.dispatchEvent(new Event('click', { bubbles: true }));
                          FlutterChannel.postMessage('checkboxSelected:' + txt);
                      }, 150);
                  })(checkbox, AUTO_SELECT_CHECKBOXES[i]);

                  break;
              }
          }
      }
  }

  // OTP fill function (called from Flutter)
  window.fillOTP = function(otp) {
      var selectors = [
          'input[type="text"][name*="otp"]',
          'input[type="number"][id*="otp"]',
          'input[placeholder*="OTP"]',
          'input[placeholder*="otp"]'
      ];

      for (var i = 0; i < selectors.length; i++) {
          var element = document.querySelector(selectors[i]);
          if (element) {
              fillInput(element, otp);
              return true;
          }
      }

      // Fallback: Fill last text input
      var textInputs = document.querySelectorAll('input[type="text"], input[type="number"]');
      if (textInputs.length > 0) {
          fillInput(textInputs[textInputs.length - 1], otp);
          return true;
      }
      return false;
  };

  // Error detection
  if (typeof window.reportedErrors === 'undefined') {
      window.reportedErrors = new Set();
  }

  function detectAndReportErrors() {
      var errors = [];

      var aadhaarError = document.getElementById('invalid_uid');
      if (aadhaarError && aadhaarError.textContent) {
          errors.push(aadhaarError.textContent.trim());
      }

      var otpError = document.getElementById('send_err');
      if (otpError && otpError.textContent) {
          errors.push(otpError.textContent.trim());
      }

      var pinError = document.getElementById('v_pin_err');
      if (pinError && pinError.textContent) {
          errors.push(pinError.textContent.trim());
      }

      if (errors.length > 0) {
          var key = errors.join(', ');
          if (!window.reportedErrors.has(key)) {
              window.reportedErrors.add(key);
              FlutterChannel.postMessage('reportError:' + key);
          }
      } else {
          window.reportedErrors.clear();
      }
  }

  // Hook OTP generate button
  function hookOTPButtons() {
      var buttons = document.querySelectorAll('button, input[type="button"], input[type="submit"]');
      for (var i = 0; i < buttons.length; i++) {
          var btn = buttons[i];
          var text = (btn.textContent || btn.value || '').toLowerCase();
          if (text.includes('otp') && text.includes('generate')) {
              btn.addEventListener('click', function() {
                  FlutterChannel.postMessage('onOTPGenerateClicked');
              });
          }
      }
  }

  // Retry mechanism
  var attempts = 0;
  var maxAttempts = 10;

  function retry() {
      if (attempts >= maxAttempts) return;
      attempts++;

      attemptAadhaarAutofill();
      scheduleEnsureActiveFieldVisible(120);
      selectAadhaarConsentCheckbox();
      selectAdditionalCheckboxes();
      hookOTPButtons();
      detectAndReportErrors();

      if (!window.digilockerAlreadyFilled || !window.consentCheckboxSelected) {
          setTimeout(retry, 500);
      }
  }

  installKeyboardHandlers();
  setTimeout(retry, 300);

  // Monitor DOM changes
  if (typeof MutationObserver !== 'undefined') {
      var observer = new MutationObserver(function() {
          attemptAadhaarAutofill();
          selectAadhaarConsentCheckbox();
          selectAdditionalCheckboxes();
          hookOTPButtons();
      });
      observer.observe(document.body, { childList: true, subtree: true });
  }

  // Check errors periodically
  setInterval(detectAndReportErrors, 2000);

  FlutterChannel.postMessage('debug:Script initialized');
})();
''';
}