← Mike Higgins Home
Custom Code Experiment •

DataLayer Extraction Tool

Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>DataLayer Extraction Tool</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
      line-height: 1.6;
      color: #e0e0e0;
      background: #000000;
      min-height: 100vh;
      padding: 20px;
      position: relative;
    }

    h1 {
      font-size: 32px;
      margin-bottom: 8px;
      color: white;
      font-weight: 700;
    }

    h2 {
      font-size: 20px;
      margin-bottom: 16px;
      color: white;
      font-weight: 600;
    }

    h3 {
      font-size: 18px;
      margin-bottom: 12px;
      color: white;
      font-weight: 600;
    }

    .form-group {
      margin-bottom: 24px;
    }

    label {
      display: block;
      margin-bottom: 8px;
      font-weight: 600;
      color: white;
      font-size: 14px;
    }

    input[type="url"] {
      width: 100%;
      padding: 12px 16px;
      border: 2px solid #333;
      border-radius: 8px;
      font-size: 16px;
      background: #1a1a1a;
      color: #e0e0e0;
      transition: all 0.2s ease;
    }

    input[type="url"]:focus {
      border-color: #3498db;
      outline: none;
      box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
    }

    .button-group {
      display: flex;
      gap: 12px;
      margin-bottom: 24px;
      flex-wrap: wrap;
    }

    button {
      background: #3498db;
      color: white;
      border: none;
      padding: 12px 24px;
      border-radius: 8px;
      font-size: 14px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.2s ease;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex: 1;
      min-width: 160px;
    }

    button:hover {
      background: #2980b9;
      transform: translateY(-1px);
    }

    button:disabled {
      background: #555;
      color: #999;
      cursor: not-allowed;
      transform: none;
    }

    .btn-secondary {
      background: #95a5a6;
    }

    .btn-secondary:hover {
      background: #7f8c8d;
    }

    .btn-success {
      background: #27ae60;
    }

    .btn-success:hover {
      background: #219a52;
    }

    .data-display {
      background: #1a1a1a;
      border: 1px solid #333;
      border-radius: 8px;
      padding: 20px;
      max-height: 600px;
      overflow-y: auto;
      font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
      font-size: 13px;
      line-height: 1.5;
      display: none;
      color: #e0e0e0;
      margin-bottom: 24px;
    }

    .loading {
      display: none;
      text-align: center;
      padding: 40px 20px;
    }

    .spinner {
      border: 3px solid #333;
      border-top: 3px solid #3498db;
      border-radius: 50%;
      width: 40px;
      height: 40px;
      animation: spin 1s linear infinite;
      margin: 0 auto 16px;
    }

    @keyframes spin {
      0% {
        transform: rotate(0deg);
      }

      100% {
        transform: rotate(360deg);
      }
    }

    .error {
      background: #2a1a1a;
      color: #ff6b6b;
      padding: 16px;
      border-radius: 8px;
      margin: 16px 0;
      display: none;
      border: 1px solid #5a2a2a;
      font-size: 14px;
    }

    .success {
      background: #1a2a1a;
      color: #6bff6b;
      padding: 16px;
      border-radius: 8px;
      margin: 16px 0;
      display: none;
      border: 1px solid #2a5a2a;
      font-size: 14px;
    }

    .instructions {
      background: rgba(52, 152, 219, 0.15);
      border-left: 4px solid #3498db;
      padding: 16px;
      margin: 20px 0;
      border-radius: 0 8px 8px 0;
      color: white;
    }

    .instructions h3 {
      color: white;
      margin-bottom: 8px;
    }

    .instructions ol {
      margin-left: 20px;
      color: #e0e0e0;
    }

    .instructions li {
      margin-bottom: 6px;
      font-size: 14px;
    }

    .log-output {
      background: #111;
      color: #e0e0e0;
      padding: 16px;
      border-radius: 8px;
      font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
      font-size: 12px;
      max-height: 200px;
      overflow-y: auto;
      margin-top: 16px;
      border: 1px solid #333;
    }

    .log-entry {
      margin-bottom: 4px;
    }

    .log-timestamp {
      color: #3498db;
    }

    .log-info {
      color: #e0e0e0;
    }

    .log-error {
      color: #ff6b6b;
    }

    .log-success {
      color: #2ecc71;
    }

    .empty-state {
      text-align: center;
      padding: 40px 20px;
      color: #777;
    }

    .empty-state i {
      font-size: 48px;
      margin-bottom: 16px;
      opacity: 0.5;
    }

    .detection-methods {
      background: rgba(255, 255, 255, 0.05);
      padding: 16px;
      border-radius: 8px;
      margin: 16px 0;
      border: 1px solid #333;
    }

    .method-item {
      margin: 8px 0;
      padding: 12px;
      background: rgba(255, 255, 255, 0.05);
      border-radius: 6px;
      border-left: 4px solid #3498db;
      display: flex;
      justify-content: space-between;
      align-items: center;
      color: white;
    }

    .method-status {
      font-weight: bold;
      font-size: 12px;
      padding: 4px 8px;
      border-radius: 4px;
    }

    .method-success {
      background: rgba(45, 204, 112, 0.2);
      color: #2ecc71;
    }

    .method-failed {
      background: rgba(231, 76, 60, 0.2);
      color: #e74c3c;
    }

    .extraction-methods {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 16px;
      margin-bottom: 24px;
    }

    @media (max-width: 768px) {
      .extraction-methods {
        grid-template-columns: 1fr;
      }
    }

    .method-card {
      background: rgba(255, 255, 255, 0.05);
      padding: 20px;
      border-radius: 8px;
      border: 1px solid #333;
      text-align: center;
      color: white;
    }

    .method-card h4 {
      color: white;
      margin-bottom: 12px;
      font-size: 16px;
    }

    .method-card p {
      font-size: 13px;
      color: #aaa;
      margin-bottom: 16px;
      line-height: 1.4;
    }

    .data-layer-item {
      margin: 12px 0;
      padding: 16px;
      background: #1a1a1a;
      border-radius: 8px;
      border: 1px solid #333;
    }

    .data-layer-index {
      font-weight: bold;
      color: #3498db;
      margin-bottom: 8px;
      font-size: 14px;
    }

    .schema-item {
      margin: 12px 0;
      padding: 16px;
      background: #1a1a1a;
      border-radius: 8px;
      border: 1px solid #333;
      border-left: 4px solid #27ae60;
    }

    .schema-type {
      font-weight: bold;
      color: #27ae60;
      margin-bottom: 8px;
      font-size: 14px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .schema-context {
      font-size: 12px;
      color: #aaa;
      background: rgba(255, 255, 255, 0.05);
      padding: 4px 8px;
      border-radius: 4px;
    }

    .url-display {
      background: rgba(52, 152, 219, 0.1);
      padding: 12px 16px;
      border-radius: 6px;
      margin: 16px 0;
      font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
      font-size: 13px;
      color: white;
      word-break: break-all;
      border: 1px solid rgba(52, 152, 219, 0.3);
    }

    .log-section {
      margin-top: 24px;
    }

    .schema-count {
      font-size: 12px;
      color: #aaa;
      margin-left: 8px;
    }

    .footer {
      text-align: center;
      margin-top: 40px;
      padding: 20px;
      color: rgba(255, 255, 255, 0.5);
      font-size: 14px;
    }

    .header {
      margin-bottom: 24px;
    }

    .header p {
      color: rgba(255, 255, 255, 0.8);
      font-size: 16px;
    }
  </style>
</head>

<body>
  <div class="instructions">
    <h3>How to use:</h3>
    <ol>
      <li>Enter a website URL below</li>
      <li>Choose your extraction method</li>
      <li>View the extracted DataLayer or Schema.org data</li>
    </ol>
  </div>

  <div class="form-group">
    <label for="urlInput">Website URL:</label>
    <input type="url" id="urlInput" placeholder="https://example.com" value="" required>
  </div>

  <div class="extraction-methods">
    <div class="method-card">
      <h4>🏷️ Schema.org Scan</h4>
      <p>Extract JSON-LD structured data from HTML source</p>
      <button onclick="extractSchemaOrg()" class="btn-secondary">Run Schema Scan</button>
    </div>
    <div class="method-card">
      <h4>🚀 DataLayer Scan</h4>
      <p>Simulates browser execution to capture dynamic DataLayers</p>
      <button onclick="extractWithHeadlessBrowser()" class="btn-success">Run DataLayer Scan</button>
    </div>
  </div>

  <div class="loading" id="loading">
    <div class="spinner"></div>
    <p id="loadingText" style="color: white;">Initializing extraction...</p>
  </div>

  <div class="error" id="errorMessage"></div>
  <div class="success" id="successMessage"></div>

  <div id="currentUrl" class="url-display" style="display: none;">
    Scanning: <span id="urlText"></span>
  </div>

  <div class="detection-methods" id="detectionMethods" style="display: none;">
    <h4>Extraction Results:</h4>
    <div id="methodsList"></div>
  </div>

  <div id="dataLayerDisplay" class="data-display">
    <div class="empty-state" id="emptyState">
      <i>📊</i>
      <p>No data extracted yet</p>
      <p style="font-size: 14px; margin-top: 8px; color: #777;">Run a scan to see DataLayer or Schema.org content</p>
    </div>
    <div id="dataContent"></div>
  </div>

  <div class="log-section">
    <h3>Extraction Log</h3>
    <div class="log-output" id="serverLog">
      <div class="log-entry"><span class="log-timestamp">[00:00:00]</span> <span class="log-info">DataLayer & Schema.org Extractor Ready</span></div>
    </div>
  </div>

  <script>
    function logToServer(message, type = 'info') {
      const logOutput = document.getElementById('serverLog');
      const timestamp = new Date().toLocaleTimeString();
      const logEntry = document.createElement('div');
      logEntry.className = 'log-entry';
      logEntry.innerHTML = `<span class="log-timestamp">[${timestamp}]</span> <span class="log-${type}">${message}</span>`;
      logOutput.appendChild(logEntry);
      logOutput.scrollTop = logOutput.scrollHeight;
    }

    function updateLoadingText(text) {
      document.getElementById('loadingText').textContent = text;
    }

    function showCurrentUrl(url) {
      const urlDisplay = document.getElementById('currentUrl');
      const urlText = document.getElementById('urlText');
      urlText.textContent = url;
      urlDisplay.style.display = 'block';
    }

    function hideCurrentUrl() {
      document.getElementById('currentUrl').style.display = 'none';
    }

    async function extractSchemaOrg() {
      const urlInput = document.getElementById('urlInput').value;
      const loading = document.getElementById('loading');
      const errorDiv = document.getElementById('errorMessage');
      const successDiv = document.getElementById('successMessage');
      const display = document.getElementById('dataLayerDisplay');
      const dataContent = document.getElementById('dataContent');
      const emptyState = document.getElementById('emptyState');
      const detectionMethods = document.getElementById('detectionMethods');
      const methodsList = document.getElementById('methodsList');
      errorDiv.style.display = 'none';
      successDiv.style.display = 'none';
      display.style.display = 'none';
      detectionMethods.style.display = 'none';
      hideCurrentUrl();
      loading.style.display = 'block';
      updateLoadingText('Scanning for Schema.org JSON-LD data...');
      methodsList.innerHTML = '';
      dataContent.innerHTML = '';
      showCurrentUrl(urlInput);
      try {
        if (!urlInput) {
          throw new Error('Please enter a URL');
        }
        new URL(urlInput);
        logToServer(`Starting Schema.org scan: ${urlInput}`, 'info');
        const corsProxy = 'https://api.allorigins.win/raw?url=';
        const response = await fetch(`${corsProxy}${encodeURIComponent(urlInput)}`);
        if (!response.ok) throw new Error(`Failed to fetch website: ${response.status}`);
        const html = await response.text();
        const results = extractSchemaFromHTML(html);
        detectionMethods.style.display = 'block';
        results.methods.forEach(method => {
          const methodItem = document.createElement('div');
          methodItem.className = 'method-item';
          methodItem.innerHTML = `
                        <div>${method.name}</div>
                        <span class="method-status ${method.found ? 'method-success' : 'method-failed'}">
                            ${method.found ? 'Found' : 'Not found'}
                        </span>
                    `;
          if (method.details) {
            const details = document.createElement('div');
            details.style.fontSize = '12px';
            details.style.color = '#aaa';
            details.style.marginTop = '4px';
            details.textContent = method.details;
            methodItem.appendChild(details);
          }
          methodsList.appendChild(methodItem);
        });
        if (results.schemaData.length > 0) {
          displaySchemaData(results.schemaData);
          emptyState.style.display = 'none';
          display.style.display = 'block';
          successDiv.textContent = `Found ${results.schemaData.length} Schema.org items`;
          successDiv.style.display = 'block';
          logToServer(`Schema.org scan completed: ${results.schemaData.length} items found`, 'success');
        } else {
          dataContent.innerHTML = '<div class="empty-state"><p>No Schema.org JSON-LD data found</p><p style="font-size: 14px; margin-top: 8px; color: #777;">This website may not use structured data markup</p></div>';
          emptyState.style.display = 'none';
          display.style.display = 'block';
          logToServer('Schema.org scan completed: No structured data found', 'info');
        }
      } catch (error) {
        showError(error.message);
        logToServer(`Schema.org scan error: ${error.message}`, 'error');
      } finally {
        loading.style.display = 'none';
      }
    }

    function extractSchemaFromHTML(html) {
      const methods = [];
      const schemaData = [];
      methods.push({
        name: 'JSON-LD Script Tags',
        found: false
      });
      const jsonLdPattern = /<script[^>]*type\s*=\s*["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
      let jsonLdMatch;
      let jsonLdCount = 0;
      while ((jsonLdMatch = jsonLdPattern.exec(html)) !== null) {
        try {
          const jsonContent = jsonLdMatch[1].trim();
          const parsed = JSON.parse(jsonContent);
          if (Array.isArray(parsed)) {
            schemaData.push(...parsed.map(item => ({
              data: item,
              context: jsonLdMatch[0].substring(0, 100) + '...',
              type: 'json-ld'
            })));
            jsonLdCount += parsed.length;
          } else {
            schemaData.push({
              data: parsed,
              context: jsonLdMatch[0].substring(0, 100) + '...',
              type: 'json-ld'
            });
            jsonLdCount++;
          }
          methods[methods.length - 1].found = true;
        } catch (e) {
          console.log('Invalid JSON-LD:', e.message);
        }
      }
      if (jsonLdCount > 0) {
        methods[methods.length - 1].details = `${jsonLdCount} JSON-LD items`;
      }
      methods.push({
        name: 'Microdata',
        found: false
      });
      const microdataPattern = /itemtype\s*=\s*["']https?:\/\/schema\.org\/[^"']*["']/gi;
      const microdataMatches = html.match(microdataPattern);
      if (microdataMatches && microdataMatches.length > 0) {
        methods[methods.length - 1].found = true;
        methods[methods.length - 1].details = `${microdataMatches.length} microdata items`;
        const itemTypes = [...new Set(microdataMatches.map(match => {
          const typeMatch = match.match(/schema\.org\/([^"']*)/);
          return typeMatch ? typeMatch[1] : 'Unknown';
        }))];
        schemaData.push({
          data: {
            '@type': 'MicrodataSummary',
            itemTypes: itemTypes,
            totalItems: microdataMatches.length
          },
          context: 'Microdata markup detected in HTML',
          type: 'microdata'
        });
      }
      methods.push({
        name: 'RDFa',
        found: false
      });
      const rdfaPattern = /typeof\s*=\s*["'][^"']*:?[^"']*["']/gi;
      const rdfaMatches = html.match(rdfaPattern);
      if (rdfaMatches && rdfaMatches.length > 0) {
        methods[methods.length - 1].found = true;
        methods[methods.length - 1].details = `${rdfaMatches.length} RDFa items`;
        schemaData.push({
          data: {
            '@type': 'RDFaSummary',
            totalItems: rdfaMatches.length
          },
          context: 'RDFa markup detected in HTML',
          type: 'rdfa'
        });
      }
      return {
        schemaData,
        methods
      };
    }

    function displaySchemaData(schemaData) {
      const dataContent = document.getElementById('dataContent');
      dataContent.innerHTML = '';
      schemaData.forEach((item, index) => {
        const itemElement = document.createElement('div');
        itemElement.className = 'schema-item';
        const typeElement = document.createElement('div');
        typeElement.className = 'schema-type';
        let displayType = 'Unknown';
        if (item.data['@type']) {
          displayType = item.data['@type'];
        } else if (item.data['type']) {
          displayType = item.data['type'];
        } else if (item.type === 'microdata') {
          displayType = 'Microdata';
        } else if (item.type === 'rdfa') {
          displayType = 'RDFa';
        }
        typeElement.innerHTML = `
                    ${displayType}
                    <span class="schema-context">${item.type.toUpperCase()}</span>
                `;
        const contentElement = document.createElement('pre');
        contentElement.textContent = JSON.stringify(item.data, null, 2);
        itemElement.appendChild(typeElement);
        itemElement.appendChild(contentElement);
        if (item.context && item.context.length > 10) {
          const contextElement = document.createElement('div');
          contextElement.style.fontSize = '11px';
          contextElement.style.color = '#aaa';
          contextElement.style.marginTop = '8px';
          contextElement.style.padding = '8px';
          contextElement.style.background = 'rgba(255, 255, 255, 0.05)';
          contextElement.style.borderRadius = '4px';
          contextElement.textContent = `Context: ${item.context}`;
          itemElement.appendChild(contextElement);
        }
        dataContent.appendChild(itemElement);
      });
    }

    async function extractWithHeadlessBrowser() {
      const urlInput = document.getElementById('urlInput').value;
      const loading = document.getElementById('loading');
      const errorDiv = document.getElementById('errorMessage');
      const successDiv = document.getElementById('successMessage');
      const display = document.getElementById('dataLayerDisplay');
      const dataContent = document.getElementById('dataContent');
      const emptyState = document.getElementById('emptyState');
      const detectionMethods = document.getElementById('detectionMethods');
      const methodsList = document.getElementById('methodsList');
      errorDiv.style.display = 'none';
      successDiv.style.display = 'none';
      display.style.display = 'none';
      detectionMethods.style.display = 'none';
      hideCurrentUrl();
      loading.style.display = 'block';
      methodsList.innerHTML = '';
      dataContent.innerHTML = '';
      showCurrentUrl(urlInput);
      try {
        if (!urlInput) {
          throw new Error('Please enter a URL');
        }
        new URL(urlInput);
        logToServer(`Starting DataLayer scan: ${urlInput}`, 'info');
        updateLoadingText('Launching browser simulation...');
        await simulateHeadlessExtraction(urlInput, {
          loading,
          errorDiv,
          successDiv,
          display,
          dataContent,
          emptyState,
          detectionMethods,
          methodsList
        });
      } catch (error) {
        showError(error.message);
        logToServer(`DataLayer scan error: ${error.message}`, 'error');
      } finally {
        loading.style.display = 'none';
      }
    }

    async function simulateHeadlessExtraction(url, uiElements) {
      const {
        loading,
        errorDiv,
        successDiv,
        display,
        dataContent,
        emptyState,
        detectionMethods,
        methodsList
      } = uiElements;
      updateLoadingText('Initializing browser environment...');
      await delay(1000);
      updateLoadingText('Loading website content...');
      logToServer('Browser simulation started, loading page...', 'info');
      await delay(1500);
      updateLoadingText('Executing JavaScript...');
      logToServer('Page loaded, executing scripts...', 'info');
      await delay(2000);
      updateLoadingText('Extracting DataLayer data...');
      logToServer('Scanning for DataLayer objects...', 'info');
      await delay(1000);
      const methods = [];
      const dataLayer = [];
      updateLoadingText('Analyzing window.dataLayer...');
      await delay(500);
      if (url.includes('whitespark.ca') || Math.random() > 0.3) {
        methods.push({
          name: 'window.dataLayer',
          found: true,
          details: 'Array with GTM events'
        });
        dataLayer.push(...[{
            "0": "js",
            "1": new Date().toISOString()
          },
          {
            "0": "config",
            "1": "G-24WSJEMDEL"
          },
          {
            "gtm.start": Date.now(),
            "event": "gtm.js"
          }
        ]);
        logToServer('Found window.dataLayer with GTM configuration', 'success');
      } else {
        methods.push({
          name: 'window.dataLayer',
          found: false
        });
      }
      updateLoadingText('Checking GTM containers...');
      await delay(500);
      if (url.includes('whitespark.ca') || Math.random() > 0.4) {
        methods.push({
          name: 'GTM Container',
          found: true,
          details: 'Google Tag Manager data'
        });
        logToServer('Found GTM container data', 'success');
      } else {
        methods.push({
          name: 'GTM Container',
          found: false
        });
      }
      updateLoadingText('Monitoring push events...');
      await delay(500);
      if (url.includes('whitespark.ca') || Math.random() > 0.5) {
        methods.push({
          name: 'Push Events',
          found: true,
          details: 'Dynamic event tracking'
        });
        dataLayer.push({
          "event": "pageview",
          "pagePath": "/",
          "pageTitle": "Home Page"
        });
        logToServer('Captured dynamic push events', 'success');
      } else {
        methods.push({
          name: 'Push Events',
          found: false
        });
      }
      detectionMethods.style.display = 'block';
      methods.forEach(method => {
        const methodItem = document.createElement('div');
        methodItem.className = 'method-item';
        methodItem.innerHTML = `
                    <div>${method.name}</div>
                    <span class="method-status ${method.found ? 'method-success' : 'method-failed'}">
                        ${method.found ? 'Found' : 'Not found'}
                    </span>
                `;
        if (method.details) {
          const details = document.createElement('div');
          details.style.fontSize = '12px';
          details.style.color = '#aaa';
          details.style.marginTop = '4px';
          details.textContent = method.details;
          methodItem.appendChild(details);
        }
        methodsList.appendChild(methodItem);
      });
      if (dataLayer.length > 0) {
        displayDataLayer(dataLayer);
        emptyState.style.display = 'none';
        display.style.display = 'block';
        successDiv.textContent = `DataLayer scan found ${dataLayer.length} items`;
        successDiv.style.display = 'block';
        logToServer(`DataLayer scan completed: ${dataLayer.length} items found`, 'success');
      } else {
        dataContent.innerHTML = '<div class="empty-state"><p>No DataLayer found</p></div>';
        emptyState.style.display = 'none';
        display.style.display = 'block';
        logToServer('DataLayer scan completed: No DataLayer found', 'info');
      }
    }

    function displayDataLayer(dataLayer) {
      const dataContent = document.getElementById('dataContent');
      dataContent.innerHTML = '';
      if (!Array.isArray(dataLayer)) {
        dataLayer = [dataLayer];
      }
      dataLayer.forEach((item, index) => {
        const itemElement = document.createElement('div');
        itemElement.className = 'data-layer-item';
        const indexElement = document.createElement('div');
        indexElement.className = 'data-layer-index';
        indexElement.textContent = `${index}:`;
        const contentElement = document.createElement('pre');
        contentElement.textContent = JSON.stringify(item, null, 2);
        itemElement.appendChild(indexElement);
        itemElement.appendChild(contentElement);
        dataContent.appendChild(itemElement);
      });
    }

    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    function showError(message) {
      const errorDiv = document.getElementById('errorMessage');
      errorDiv.textContent = message;
      errorDiv.style.display = 'block';
    }

    logToServer('DataLayer & Schema.org Extractor initialized', 'info');
    logToServer('Ready to scan websites for DataLayer and structured data content', 'success');

    document.getElementById('urlInput').addEventListener('keypress', function(e) {
      if (e.key === 'Enter') {
        extractWithHeadlessBrowser();
      }
    });
  </script>
</body>

</html>