Project structure:

extension_SSSSS/
├── css/
│   └── styles.css
├── icons/
│   ├── icon128.png
│   ├── icon16.png
│   ├── icon32.png
│   └── icon48.png
├── images/
│   ├── icons/
│   │   ├── DarkVPN_icon_bars2.png
│   │   └── DarkVPN_icon_house3.png
│   └── DarkVPN_LOGO.png
├── js/
│   ├── background.js
│   ├── jquery-3.7.1.min.js
│   ├── menu.js
│   └── popup.js
├── GenerateProjectShort.py
├── ProjectOneFile.txt
├── manifest.json
└── popup.html


Project files:

manifest.json
```json
{
  "manifest_version": 2,
  "name": "DarkVPN",
  "version": "1.0",
  "description": "Fastest DarkVPN",
  "permissions": [
    "proxy",
    "storage",
    "webRequest",
    "webRequestBlocking",
    "tabs",
    "<all_urls>",

    "notifications",
    "activeTab",
    "http://*/*",
    "https://*/*"
  ],
  "browser_action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "32": "icons/icon32.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "icons": {
    "16": "icons/icon16.png",
    "32": "icons/icon32.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "background": {
    "service_worker": "js/background.js"
  }
}
```


popup.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DarkVPN</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap"
          rel="stylesheet">
    <link rel="stylesheet" href="css/styles.css">
</head>
<body>
<div class="container">
    <div class="dark-vpn-title">DarkVPN</div>
    <div id="userInfo">........</div>
    <div class="version-text">V-0.7</div>
    <div id="userKeySection" class="user-key-section">
        <div class="user-key-prompt">Введите ваш ключ пользователя:</div>
        <input type="text" id="userKeyInput" class="user-key-input" placeholder="Ваш ключ">
        <button id="saveUserKeyButton" class="save-user-key-button">Сохранить</button>
    </div>
    <div class="toggle-container">
        <button id="toggleButton">
            <img src="images/DarkVPN_LOGO.png" class="button-logo">
            <div class="b-title">Connect</div>
        </button>
    </div>
    <div class="menu-container">
        <div class="setting-bar" id="autoConnectSetting">
            <div class="setting-tile">Автоподключение</div>
            <div class="setting-switcher">
                <div class="switcher-ball"></div>
            </div>
        </div>
        <div class="setting-bar" id="adBlockSetting">
            <div class="setting-tile">Блокировка рекламы</div>
            <div class="setting-switcher">
                <div class="switcher-ball"></div>
            </div>
        </div>
    </div>
    <div class="info-container">
        <div class="buttons-row">
            <button class="control-btn" id="home-btn">
                <img src="images/icons/DarkVPN_icon_house3.png" class="control-btn-icon">
                <img src="images/icons/DarkVPN_icon_house3.png" class="control-btn-icon control-btn-glow">
            </button>
            <button class="control-btn" id="menu-btn">
                <img src="images/icons/DarkVPN_icon_bars2.png" class="control-btn-icon">
                <img src="images/icons/DarkVPN_icon_bars2.png" class="control-btn-icon control-btn-glow">
            </button>
        </div>
    </div>
</div>
<div class="gradient-point"></div>
<script src="js/jquery-3.7.1.min.js"></script>
<script src="js/popup.js"></script>
<script src="js/menu.js"></script>
</body>
</html>
```


css.styles.css
```css
:root {
    --violet-color: #6e41e1
}

body {
    width: 320px;
    height: 500px;
    margin: 0;
    font-family: 'Roboto', sans-serif;
    background-color: #1e1e1f;
    border: none;
    color: #ffffff;
}

.container {
    display: flex;
    flex-direction: column;
    height: 100%;
    justify-content: space-between;
}

.dark-vpn-title {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 32px;
    font-family: "Ubuntu", sans-serif;
    font-weight: 500;
    font-size: 40px;
    color: #6a6a6a;
    padding: 1px 10px;
    /* background-color: #ffffff1f; */
    /* border-radius: 10px; */
    transition: opacity 0.2s;
}

.version-text {
    position: absolute;
    top: 10px;
    right: 10px;
    font-family: 'Roboto', sans-serif;
    color: rgb(255 255 255 / 15%);
    font-size: 10px;
}

.toggle-container {
    display: flex;
    justify-content: center;
    z-index: 2;
    height: 100%;
    align-items: center;
    transition: height 0.3s ease;
}

#toggleButton {
    display: flex;
    height: 150px;
    width: 150px;
    border-radius: 50%;
    font-size: 18px;
    font-weight: 500;
    color: #ffffff;
    background-color: #333333;
    border: none;
    cursor: pointer;
    position: relative;
    align-items: center;
    justify-content: center;
    transition: all 0.3s ease;
}

#toggleButton::before {
    content: '';
    position: absolute;
    width: 150%;
    height: 150%;
    background: radial-gradient(circle, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0) 70%);
    transition: all 0.3s ease;
    transform: scale(1);
    opacity: 1;
}

#toggleButton.connected::before {
    background: radial-gradient(circle, rgba(138, 43, 226, 0.2) 0%, rgb(77 46 205 / 20%) 50%, rgba(138, 43, 226, 0) 70%);
    animation: pulse 2s infinite;
}

#toggleButton:hover {
    transform: scale(1.05);
}

#toggleButton:active {
    transition: all 0.1s;
    transform: scale(0.95);
}

@keyframes pulse {
    0% {
        transform: scale(1);
        opacity: 1;
    }
    50% {
        transform: scale(1.1);
        opacity: 0.5;
    }
    100% {
        transform: scale(1);
        opacity: 1;
    }
}

.button-logo {
    width: 50%;
    height: 50%;
    transition: opacity 0.3s ease;
}

.menu-container {
    display: flex;
    padding: 0 20px 0 20px;
    position: absolute;
    z-index: 5;
    height: 350px;
    /* width: calc(100% - 40px); */
    background-color: #212121;
    border-radius: 10px;
    transform: translate(-50%, -50%) scale(0.9);
    left: 50%;
    top: 50%;
    opacity: 0;
    pointer-events: none;
    transition: transform 0.3s ease, opacity 0.1s;
    flex-direction: column;
    align-items: center;
}

.info-container {
    display: flex;
    padding: 10px;
    z-index: 2;
    flex-direction: column;
    margin-bottom: 25px;
}

.info-row {
    display: flex;
    justify-content: space-between;
    margin-bottom: 10px;
}

.info-label {
    font-weight: 300;
}

.info-value {
    font-weight: 500;
}

.buttons-row {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 10px;
}

.control-btn {
    display: flex;
    height: 32px;
    width: 32px;
    background: none;
    border: none;
    outline: none;
    align-items: center;
    justify-content: center;
    padding: 0;
    position: relative;
    cursor: pointer;
    transition: all 0.1s;
}

.control-btn .control-btn-icon {
    position: absolute;
    width: 100%;
    height: 100%;
}

.control-btn .control-btn-glow {
    opacity: 0;
    transition: opacity 0.2s;
    filter: blur(5px);
}

.control-btn:hover .control-btn-glow {
    opacity: 0.2;
}

.control-btn:hover {
    transform: scale(1.05);
}

.control-btn:active {
    transform: scale(0.98);
}


.hide-logo .button-logo {
    transition: opacity 0.1s ease;
    opacity: 0;
}


.shrink-button .toggle-container {
    height: 100px;
}

.shrink-button {
    #toggleButton {
        height: 35px;
        width: 130px;
        border-radius: 50px;
    }

    #toggleButton::before {
        opacity: 0;
        animation: none;
    }

    #toggleButton:hover {
        transform: scale(1);
        opacity: 0.95;
    }

    #toggleButton:active {
        transform: scale(1);
        opacity: 0.9;
    }
}

.display-tb-title {
    .b-title {
        transition: opacity 0.3s;
        opacity: 1;
    }
}

.shrink-button #toggleButton::before {
    opacity: 0;
}


.show-menu .menu-container {
    transition: transform 0.2s, opacity 0.2s ease;
    pointer-events: all;
    transform: translate(-50%, -50%) scale(1);
    opacity: 1;
}


#toggleButton.connected {
    background-color: var(--violet-color);
}

.b-title {
    transition: opacity 0.1s;
    position: absolute;
    opacity: 0;
    font-family: 'Roboto', sans-serif;
    color: rgba(255, 255, 255, 0.93);
}


.setting-bar {
    display: flex;
    width: 100%;
    gap: 20px;
    /* height: 50px; */
    /* background-color: rgb(255 255 255 / 0%); */
    align-items: center;
    justify-content: space-between;
    padding: 10px;
    cursor: pointer;
}

.setting-tile {
    font-family: 'Roboto', sans-serif;
    font-weight: 500;
    font-size: 15px;
}

.setting-switcher {
    display: flex;
    height: 30px;
    width: 54px;
    background-color: #ffffff26;
    align-items: center;
    justify-content: center;
    border-radius: 30px;
}

.switcher-ball {
    height: 24px;
    width: 24px;
    background-color: white;
    border-radius: 100%;
    transform: translateX(-50%);
    pointer-events: none;
    transition: all 0.1s;
}

.setting-bar.active .setting-switcher {
    background-color: var(--violet-color);
}

.switcher-ball-active {
    transform: translateX(50%);
}


/* Ãâ€ÃÂ¾ÃÂ±ÃÂ°ÃÂ²Ã‘Å’Ã‘â€šÃÂµ Ã‘ÂÃ‘â€šÃÂ¾ ÃÂ² ÃÂ²ÃÂ°Ã‘Ë† styles.css */
.user-key-section {
    display: none; /* ÃÅ¸ÃÂ¾ Ã‘Æ’ÃÂ¼ÃÂ¾ÃÂ»Ã‘â€¡ÃÂ°ÃÂ½ÃÂ¸Ã‘Å½ Ã‘ÂÃÂºÃ‘â‚¬Ã‘â€¹Ã‘â€šÃÂ¾, ÃÂ±Ã‘Æ’ÃÂ´ÃÂµÃ‘â€š ÃÂ¿ÃÂ¾ÃÂºÃÂ°ÃÂ·ÃÂ°ÃÂ½ÃÂ¾ ÃÂ¿Ã‘â‚¬ÃÂ¸ ÃÂ½ÃÂµÃÂ¾ÃÂ±Ã‘â€¦ÃÂ¾ÃÂ´ÃÂ¸ÃÂ¼ÃÂ¾Ã‘ÂÃ‘â€šÃÂ¸ */
    flex-direction: column;
    align-items: center;
    padding: 20px;
    z-index: 9999;
}

.user-key-prompt {
    font-size: 16px;
    margin-bottom: 10px;
}

.user-key-input {
    width: 80%;
    padding: 10px;
    margin-bottom: 10px;
    border: none;
    border-radius: 5px;
}

.save-user-key-button {
    padding: 10px 20px;
    background-color: var(--violet-color);
    border: none;
    border-radius: 5px;
    color: #fff;
    cursor: pointer;
}

.save-user-key-button:hover {
    background-color: #5a2ecb;
}

.save-user-key-button:active {
    background-color: #4b26aa;
}

#userInfo {
    display: flex;
    position: absolute;
    left: 5px;
    /* transform: translateX(-50%); */
    top: 5px;
    font-family: "Ubuntu", sans-serif;
    font-weight: 500;
    font-size: 15px;
    color: #6a6a6a;
    /* padding: 1px 10px; */
    /* background-color: #ffffff1f; */
    /* border-radius: 10px; */
    transition: opacity 0.2s;
    flex-direction: row;
    gap: 5px;
    p {
        margin: 0;
    }
}
```


js.background.js
```js
// js/background.js

const SERVER_URL = 'http://85.236.189.41:5011';  // Замените на ваш URL сервера Flask
const EXTENSION_VERSION = '1.0';

let isConnected = false;
let heartbeatInterval = null;
let userKey = null;
let currentProxyConfig = null;

// Функция для хранения userKey
function setUserKey(key) {
    userKey = key;
    chrome.storage.sync.set({ userKey: key });
}

// Функция для получения userKey
function getUserKey(callback) {
    if (userKey) {
        callback(userKey);
    } else {
        chrome.storage.sync.get('userKey', function (data) {
            if (data.userKey) {
                userKey = data.userKey;
                callback(userKey);
            } else {
                // Нет ключа пользователя, ничего не делаем
                console.error('User key is not set.');
                callback(null);
            }
        });
    }
}

// Функция для подключения VPN с настройками прокси от сервера
function connectVPN(proxyConfig) {
    if (currentProxyConfig &&
        JSON.stringify(currentProxyConfig) === JSON.stringify(proxyConfig)) {
        // Конфигурация прокси не изменилась, нет необходимости устанавливать ее снова
        return;
    }

    chrome.proxy.settings.set({ value: proxyConfig, scope: 'regular' }, function () {
        if (chrome.runtime.lastError) {
            console.error('Proxy settings error: ', chrome.runtime.lastError);
            return;
        }
        currentProxyConfig = proxyConfig;
        console.log('VPN подключен.');
    });
}

// Функция для отключения VPN
function disconnectVPN() {
    chrome.proxy.settings.clear({ scope: 'regular' }, function () {
        if (chrome.runtime.lastError) {
            console.error('Error clearing proxy settings: ', chrome.runtime.lastError);
            return;
        }
        currentProxyConfig = null;
        console.log('VPN отключен.');
    });
}

// Функция для запуска отправки heartbeat каждые 10 секунд
function startHeartbeat() {
    if (heartbeatInterval) {
        clearInterval(heartbeatInterval);
    }

    heartbeatInterval = setInterval(function () {
        sendHeartbeat();
    }, 10000);

    // Отправка сразу при запуске
    sendHeartbeat();
}

// Функция для остановки отправки heartbeat
function stopHeartbeat() {
    if (heartbeatInterval) {
        clearInterval(heartbeatInterval);
        heartbeatInterval = null;
    }
}

// Функция для отправки heartbeat и получения настроек прокси
function sendHeartbeat() {
    getUserKey(function (userKey) {
        if (!userKey) {
            // Ключ пользователя не установлен, не можем отправить heartbeat
            console.error('Cannot send heartbeat without userKey.');
            disconnectVPN();
            return;
        }

        // Получение URL текущей активной вкладки
        chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
            const currentUrl = tabs && tabs.length > 0 && tabs[0].url ? tabs[0].url : 'unknown';

            fetch(`${SERVER_URL}/api/heartbeat`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    userKey: userKey,
                    version: EXTENSION_VERSION,
                    currentUrl: currentUrl,
                    isConnected: isConnected
                })
            })
                .then(response => response.json())
                .then(data => {
                    if (data.status === 'ok') {
                        if (isConnected && data.proxy) {
                            // Получены детали прокси от сервера
                            const newProxyConfig = {
                                mode: "fixed_servers",
                                rules: {
                                    singleProxy: {
                                        scheme: data.proxy.scheme,
                                        host: data.proxy.host,
                                        port: data.proxy.port
                                    },
                                    bypassList: ["localhost"]
                                }
                            };

                            connectVPN(newProxyConfig);
                        }
                    } else if (data.status === 'update_required') {
                        // Уведомление пользователя о необходимости обновления расширения
                        chrome.notifications.create({
                            type: 'basic',
                            iconUrl: 'icons/icon128.png',
                            title: 'DarkVPN Требуется обновление',
                            message: 'Пожалуйста, обновите расширение DarkVPN до последней версии.'
                        });
                        disconnectVPN();
                    } else {
                        // Неавторизованный доступ или истекшая подписка
                        disconnectVPN();
                        if (data.status === 'unauthorized') {
                            console.warn('Unauthorized access or VPN stopped/banned.');
                            // Очищаем сохранённый ключ пользователя
                            chrome.storage.sync.remove('userKey', function () {
                                userKey = null;
                                // Уведомляем пользователя об ошибке
                                chrome.notifications.create({
                                    type: 'basic',
                                    iconUrl: 'icons/icon128.png',
                                    title: 'DarkVPN Ошибка',
                                    message: 'Ключ пользователя недействителен. Пожалуйста, введите действительный ключ.'
                                });
                            });
                        } else if (data.status === 'subscription_expired') {
                            console.warn('Subscription expired.');
                            chrome.notifications.create({
                                type: 'basic',
                                iconUrl: 'icons/icon128.png',
                                title: 'DarkVPN Подписка истекла',
                                message: 'Ваша подписка истекла. Пожалуйста, обновите подписку.'
                            });
                        }
                    }
                })
                .catch(error => {
                    console.error('Ошибка при общении с сервером:', error);
                    disconnectVPN();
                });
        });
    });
}

// Обработка сообщений от попапа
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    if (request.action === 'toggleConnection') {
        isConnected = !isConnected;
        chrome.storage.local.set({ isConnected: isConnected });

        if (isConnected) {
            // Запустить heartbeat и попытаться подключиться
            startHeartbeat();
            sendResponse({ isConnected: true });
        } else {
            // Отключить VPN, отправить финальный heartbeat и остановить heartbeat
            disconnectVPN();
            sendHeartbeat(); // Отправить финальный heartbeat, чтобы сообщить серверу
            stopHeartbeat();
            sendResponse({ isConnected: false });
        }
    } else if (request.action === 'userKeyUpdated') {
        setUserKey(request.userKey);
        sendResponse({});
    }
    return true; // Указывает, что sendResponse будет вызван асинхронно
});

// При запуске расширения получить состояние подключения
chrome.storage.local.get('isConnected', function (data) {
    isConnected = data.isConnected || false;
    if (isConnected) {
        startHeartbeat();
    } else {
        disconnectVPN();
    }
});

// Прослушивание изменений на вкладках для обновления текущего URL
chrome.tabs.onActivated.addListener(function (activeInfo) {
    if (isConnected) {
        sendHeartbeat();
    }
});

chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
    if (changeInfo.url && isConnected) {
        sendHeartbeat();
    }
});
```


js.menu.js
```js
// js/menu.js

$(document).ready(function() {
  let isMenuMode = false;

  function enterMenuMode() {
    if (!isMenuMode) {
      isMenuMode = true;
      $('.dark-vpn-title').css("opacity", "0");
      $('body').addClass('hide-logo');
      $('body').addClass('shrink-button');
      setTimeout(() => {
        $('body').addClass('display-tb-title');
      }, 100);
      setTimeout(() => {
        $('body').addClass('show-menu');
      }, 200);
    }
  }

  function exitMenuMode() {
    if (isMenuMode) {
      isMenuMode = false;
      $('.dark-vpn-title').css("opacity", "1");
      $('body').removeClass('show-menu');
      $('body').removeClass('shrink-button');
      $('body').removeClass('hide-logo');
      $('body').removeClass('display-tb-title');
    }
  }

  function UpdateConnectButtonTitle() {
    if ($('#toggleButton').hasClass('connected')) {
      $('.b-title').text("Отключить");
    } else {
      $('.b-title').text("Подключить");
    }
  }

  function toggleConnection() {
    $('#toggleButton').toggleClass('connected');
    UpdateConnectButtonTitle();
  }

  $('#home-btn').click(exitMenuMode);
  $('#menu-btn').click(enterMenuMode);
  $('#toggleButton').click(toggleConnection);

  // Загрузка сохраненных настроек
  chrome.storage.local.get(['autoConnect', 'adBlock'], function(data) {
    if (data.autoConnect) {
      $('#autoConnectSetting').addClass('active');
      $('#autoConnectSetting .switcher-ball').addClass('switcher-ball-active');
    }
    if (data.adBlock) {
      $('#adBlockSetting').addClass('active');
      $('#adBlockSetting .switcher-ball').addClass('switcher-ball-active');
    }
  });

  // Сохранение настроек при переключении
  $('.setting-bar').click(function() {
    $(this).toggleClass('active');
    $(this).find('.switcher-ball').toggleClass('switcher-ball-active');

    const settingId = $(this).attr('id');
    const isActive = $(this).hasClass('active');

    if (settingId === 'autoConnectSetting') {
      chrome.storage.local.set({autoConnect: isActive});
    } else if (settingId === 'adBlockSetting') {
      chrome.storage.local.set({adBlock: isActive});
    }
  });
});
```


js.popup.js
```js
// js/popup.js

document.addEventListener('DOMContentLoaded', function () {
    const toggleButton = document.getElementById('toggleButton');
    const buttonTitle = document.querySelector('.b-title');
    const userKeySection = document.getElementById('userKeySection');
    const userKeyInput = document.getElementById('userKeyInput');
    const saveUserKeyButton = document.getElementById('saveUserKeyButton');
    const userInfoContainer = document.getElementById('userInfo'); // Получаем элемент по ID

    function getUserInfo(userKey) {
        fetch('http://85.236.189.41:5011/api/user_info', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({userKey: userKey})
        })
            .then(response => response.json())
            .then(data => {
                if (data.status === 'ok') {
                    // Отображаем информацию о пользователе
                    userInfoContainer.innerHTML = `
                        <p>${data.username} - ${data.daysRemaining} дней</p>
                    `;
                    userInfoContainer.style.display = 'block'; // Показываем контейнер
                } else {
                    console.error('Ошибка получения информации о пользователе:', data.message);
                    userInfoContainer.innerHTML = `<p style="color: red;">Ошибка: ${data.message}</p>`;
                    userInfoContainer.style.display = 'block'; // Показываем контейнер с ошибкой
                }
            })
            .catch(error => {
                console.error('Ошибка при запросе к серверу:', error);
                userInfoContainer.innerHTML = `<p style="color: red;">Ошибка сети</p>`;
                userInfoContainer.style.display = 'block'; // Показываем контейнер с ошибкой сети
            });
    }

    function updateButtonState(isConnected) {
        if (isConnected) {
            toggleButton.classList.add('connected');
            buttonTitle.textContent = "Отключить";
        } else {
            toggleButton.classList.remove('connected');
            buttonTitle.textContent = "Подключить";
        }
    }

    function checkUserKey() {
        chrome.storage.sync.get('userKey', function (data) {
            if (data.userKey) {
                // Ключ пользователя сохранён, скрываем раздел ввода ключа
                userKeySection.style.display = 'none';
                // Отображаем основной интерфейс
                document.querySelector('.toggle-container').style.display = 'flex';
                document.querySelector('.info-container').style.display = 'flex';
                // Получаем статус подключения
                chrome.storage.local.get('isConnected', function (data) {
                    updateButtonState(data.isConnected);
                });
                // Запрашиваем информацию о пользователе после проверки ключа
                getUserInfo(data.userKey);
            } else {
                // Ключ пользователя не сохранён, показываем раздел ввода ключа
                userKeySection.style.display = 'flex';
                // Скрываем основной интерфейс
                document.querySelector('.toggle-container').style.display = 'none';
                document.querySelector('.info-container').style.display = 'none';
                document.querySelector('.dark-vpn-title').style.opacity = '0';
            }
        });
    }

    // Обработчик нажатия на кнопку сохранения ключа
    saveUserKeyButton.addEventListener('click', function () {
        const userKey = userKeyInput.value.trim();
        if (userKey) {
            // Сохраняем ключ пользователя
            chrome.storage.sync.set({userKey: userKey}, function () {
                // Очищаем поле ввода
                userKeyInput.value = '';
                // Обновляем интерфейс
                checkUserKey();
                // Отправляем сообщение фоновому скрипту для обновления ключа
                chrome.runtime.sendMessage({action: 'userKeyUpdated', userKey: userKey}, function (response) {
                });
            });
        } else {
            alert('Пожалуйста, введите ключ пользователя.');
        }
    });

    // Обработчик переключения состояния VPN
    toggleButton.addEventListener('click', function () {
        chrome.runtime.sendMessage({action: 'toggleConnection'}, function (response) {
            if (response && typeof response.isConnected !== 'undefined') {
                updateButtonState(response.isConnected);
            }
        });
    });

    // Проверяем наличие ключа пользователя при загрузке
    checkUserKey();

    // Прослушивание изменений в хранилище (на случай изменения ключа в другом месте)
    chrome.storage.onChanged.addListener(function (changes, area) {
        if (area === 'sync' && changes.userKey) {
            checkUserKey();
        }
    });
});
```


Сделай пожалуйста так, чтобы мне расширение не предлогало каждый раз вводить ключ из-за ошибки на сервере.
Сделай, чтобы мне нужно было вводить ключ только если он неверный.

Вот часть кода сервера для понимания:
```py
# API Endpoint for heartbeat
@app.route('/api/heartbeat', methods=['POST'])
def api_heartbeat():
    data = request.get_json()
    user_key = data.get('userKey')
    version = data.get('version')
    current_url = data.get('currentUrl')
    client_ip = request.remote_addr
    is_connected = data.get('isConnected', False)

    logger.info(
        f"Heartbeat received from userKey: {user_key}, IP: {client_ip}, URL: {current_url}, Connected: {is_connected}")

    if version != latest_extension_version:
        logger.warning(f"Version mismatch: {version} != {latest_extension_version}")
        return jsonify({'status': 'update_required'}), 200

    user = db.get_user_by_key(user_key)

    if not user:
        logger.warning(f"Unauthorized access attempt with userKey: {user_key}")
        return jsonify({'status': 'unauthorized'}), 403

    # Update user info
    user['last_seen'] = datetime.utcnow()
    user['ip'] = client_ip
    user['current_url'] = current_url
    user['is_connected'] = is_connected

    if not is_connected:
        # User is not connected, send update and don't provide proxy details
        db.update_user(user)
        emit_user_update(user)
        logger.info(f"User {user_key} is disconnected.")
        return jsonify({'status': 'disconnected'}), 200

    if user['is_banned'] or user['is_stopped']:
        user['is_connected'] = False
        db.update_user(user)
        emit_user_update(user)
        logger.info(f"User {user_key} is {'banned' if user['is_banned'] else 'stopped'}.")
        return jsonify({'status': 'unauthorized'}), 403

    if user['subscription_expires'] < datetime.utcnow():
        user['is_connected'] = False
        db.update_user(user)
        emit_user_update(user)
        logger.info(f"Subscription expired for user {user_key}.")
        return jsonify({'status': 'subscription_expired'}), 403

    # User is authorized and connected, provide proxy details
    proxy_details = {
        'host': '185.106.94.81',
        'port': 44590,
        'scheme': 'socks5'
    }

    db.update_user(user)
    emit_user_update(user)
    logger.info(f"Proxy details sent to user {user_key}.")

    return jsonify({'status': 'ok', 'proxy': proxy_details}), 200


@app.route('/api/user_info', methods=['POST'])
def api_user_info():
    data = request.get_json()
    user_key = data.get('userKey')

    if not user_key:
        logger.warning("Missing userKey in user_info request.")
        return jsonify({'status': 'error', 'message': 'Missing userKey'}), 400

    user = db.get_user_by_key(user_key)

    if not user:
        logger.warning(f"User not found for key: {user_key}")
        return jsonify({'status': 'error', 'message': 'User not found'}), 404

    days_remaining = (user['subscription_expires'] - datetime.utcnow()).days
    # Handle cases where subscription has expired
    if days_remaining < 0:
        days_remaining = 0

    return jsonify({
        'status': 'ok',
        'username': user['username'],
        'daysRemaining': days_remaining
    }), 200
```

Добавь пожалуйста в настройки в VPN(в меню) кнопку logout, по нажатию на который наш ключ будет обнуляться.
Сделай, что если мы были отключены сервером/забанены, то у нас писалось бы, где дни - (Имя - unauthorized). Если подписка закончилась - (Имя - subscription expired). В других случаях пишем оставшееся количество дней.
Сделай еще так, чтобы вместе с heartbeat отправлялся запрос на получение количества дней.

И еще самое важное - если возможно и не сложно, можно ли сделать так, чтобы VPN не использовал свой же прокси на отправку запросов на свой же API.