Raw Source
jhult / AWS console - copy multi-session page link

// ==UserScript==
// @name         AWS console - copy multi-session page link
// @description  Adds a button which copies a multi-session page link (which works across accounts/sessions) to the clipboard.

// @namespace    https://jonathanhult.com
// @author       Jonathan Hult
// @copyright    2025, jhult (https://openuserjs.org/users/jhult)
// @version      1.0.4

// @updateURL    https://openuserjs.org/meta/jhult/AWS_Console_-_Share_Link_Button.meta.js
// @downloadURL  https://openuserjs.org/install/jhult/AWS_Console_-_Share_Link_Button.user.js

// @license      MIT
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDMyIDMyIj48cGF0aCBkPSJNMTUuNjMgMzEuMzg4bC03LjEzNS0yLjU2VjE4LjM3M2w3LjEzNSAyLjQzem0xLjMgMGw3LjEzNS0yLjU2VjE4LjM3M2wtNy4xMzUgMi40MzJ6bS03LjctMTMuOGw3LjItMi4wMzMgNi42OTYgMi4xNi02LjY5NiAyLjI3M3ptLTIuMDkyLS44TDAgMTQuMjJWMy43NWw3LjEzNSAyLjQzem0xLjMwNyAwbDcuMTM1LTIuNTZWMy43NUw4LjQ0MyA2LjE5MnptLTcuNy0xMy44bDcuMi0yLjA0MyA2LjY5NiAyLjE2LTYuNjk2IDIuMjczem0yMy4wNTIgMTMuOGwtNy4xMzUtMi41NlYzLjc1bDcuMTM1IDIuNDN6bTEuMyAwbDcuMTM1LTIuNTZWMy43NWwtNy4xMzUgMi40M3ptLTcuNy0xMy44bDcuMi0yLjAzMyA2LjY5NiAyLjE2LTYuNjk2IDIuMjczeiIgZmlsbD0iI2Y5MCIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+

// @match        https://*.*.*.console.aws.amazon.com/*
// @match        https://console.aws.amazon.com/*
// @grant        GM_setClipboard
// @run-at       document-idle
// ==/UserScript==

(function () {
  'use strict';

  // Function to create the share button
  function createShareButton() {
    // Check if button already exists
    if (document.getElementById('aws-share-button')) {
      return;
    }

    // Find the scallop icon container
    const scallop = document.getElementById('awsc-nav-scallop-icon-container');
    if (!scallop) {
      // If we can't find it, try again in a moment
      setTimeout(createShareButton, 500);
      return;
    }

    // Create share button container with similar styling
    const shareContainer = document.createElement('div');
    shareContainer.id = 'aws-share-button-container';
    shareContainer.className = '_scallop-icon-container_1s953_21';
    shareContainer.style.marginLeft = '10px';
    shareContainer.style.marginTop = '15px';

    // Create the button with similar styling to CloudShell icon
    const shareButton = document.createElement('a');
    shareButton.id = 'aws-share-button';
    shareButton.className = 'globalNav-42226 globalNav-4211 globalNav-4212 _icon-color_1s953_5';
    shareButton.href = 'javascript:void(0);';
    shareButton.title = 'Share Link';
    shareButton.style.display = 'flex';
    shareButton.style.alignItems = 'center';
    shareButton.style.justifyContent = 'center';

    // Create SVG icon for share
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('width', '16');
    svg.setAttribute('height', '16');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('fill', 'none');
    svg.setAttribute('stroke', 'currentColor');
    svg.setAttribute('stroke-width', '2');
    svg.setAttribute('stroke-linecap', 'round');
    svg.setAttribute('stroke-linejoin', 'round');

    // Share icon paths
    const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path1.setAttribute('d', 'M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8');
    svg.appendChild(path1);

    const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path2.setAttribute('d', 'M16 6l-4-4-4 4');
    svg.appendChild(path2);

    const path3 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path3.setAttribute('d', 'M12 2v13');
    svg.appendChild(path3);

    // Add SVG to button
    shareButton.appendChild(svg);

    // Add click event
    shareButton.onclick = function (e) {
      e.preventDefault();
      generateShareableLink();
    };

    // Add button to container
    shareContainer.appendChild(shareButton);

    // Insert after CloudShell icon
    scallop.parentNode.insertBefore(shareContainer, scallop.nextSibling);

    // Create notification element (hidden initially)
    const notification = document.createElement('div');
    notification.id = 'aws-share-notification';
    notification.style.position = 'fixed';
    notification.style.top = '50px';
    notification.style.right = '20px';
    notification.style.backgroundColor = '#37475A';
    notification.style.color = 'white';
    notification.style.padding = '10px';
    notification.style.borderRadius = '3px';
    notification.style.zIndex = '9999';
    notification.style.display = 'none';
    notification.style.maxWidth = '300px';
    notification.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';

    document.body.appendChild(notification);
  }

  // Function to generate a shareable link
  function generateShareableLink() {
    // Get current URL
    const currentUrl = window.location.href;

    // Extract the path and query parameters
    const urlParts = currentUrl.match(/https:\/\/([^.]+)[.]([^.]+)[.]console[.]aws[.]amazon[.]com\/([^?]+)(\?[^#]*)?(?:#(.*))?/);

    if (!urlParts) {
      showNotification('Unable to parse the current URL.', 'error');
      return;
    }

    // Extract components
    const accountId = urlParts[1].split('-')[0]; // Extract account ID
    const region = urlParts[2];
    const service = urlParts[3];
    const queryParams = urlParts[4] || '';
    const hash = urlParts[5] ? '#' + urlParts[5] : '';

    // Build generic URL
    let genericUrl = `https://console.aws.amazon.com/${service}`;

    // Add region to query params if not already there
    let updatedQueryParams = queryParams;
    if (!queryParams.includes('region=')) {
      updatedQueryParams = queryParams ? queryParams + '&region=' + region : '?region=' + region;
    }

    genericUrl += updatedQueryParams + hash;

    // Copy to clipboard
    copyToClipboard(genericUrl);

    // Show notification
    showNotification('Shareable link copied to clipboard!', 'success');
  }

  // Function to copy text to clipboard
  function copyToClipboard(text) {
    if (typeof GM_setClipboard !== 'undefined') {
      GM_setClipboard(text);
    }
    else {
      const textarea = document.createElement('textarea');
      textarea.value = text;
      textarea.style.position = 'fixed';
      textarea.style.opacity = '0';
      document.body.appendChild(textarea);
      textarea.select();
      document.execCommand('copy');
      document.body.removeChild(textarea);
    }
  }

  // Function to show notification
  function showNotification(message, type) {
    const notification = document.getElementById('aws-share-notification');
    if (!notification) return;

    notification.textContent = message;
    notification.style.backgroundColor = type === 'error' ? '#D13212' : '#1E8900';
    notification.style.display = 'block';

    // Hide after 3 seconds
    setTimeout(() => {
      notification.style.display = 'none';
    }, 3000);
  }

  // Initialize after a short delay to ensure the page is loaded
  setTimeout(createShareButton, 1500);

  // Re-initialize when URL changes (for single-page applications)
  let lastUrl = location.href;
  new MutationObserver(() => {
    if (location.href !== lastUrl) {
      lastUrl = location.href;
      setTimeout(createShareButton, 1500);
    }
  }).observe(document, {
    subtree: true,
    childList: true
  });

})();