@web-terminal/terminal
Version:
Embeddable web terminal component
1 lines • 32.2 kB
JavaScript
!function(e){"use strict";class t{constructor(e={}){this.options=e,this.isInitialized=!1,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",(()=>this.init())):this.init()}init(){this.isInitialized||this.loadDependencies().then((()=>{this.injectStyles(),this.createHTML(),this.setupConfig(),this.terminalLogic(),this.isInitialized=!0})).catch((e=>{console.error("Failed to initialize WebTerminalEmbed:",e)}))}injectStyles(){const e=document.createElement("style");e.id="web-terminal-styles",e.textContent='\n #terminalWrapper {\n position: fixed;\n bottom: -450px;\n left: 0;\n right: 0;\n height: 450px;\n background-color: rgba(0, 0, 0, 0.9);\n z-index: 9999;\n display: flex;\n flex-direction: column;\n transition: bottom 0.3s ease-in-out;\n border-top: 3px solid #333;\n }\n\n #terminalToggle {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 50px;\n height: 40px;\n border-radius: 5px;\n background-color: #000000;\n color: white;\n border: 1px solid white;\n display: flex;\n justify-content: center;\n align-items: center;\n cursor: pointer;\n z-index: 10000;\n font-size: 20px;\n font-family: monospace;\n font-weight: bold;\n box-shadow: 0 2px 5px rgba(0,0,0,0.3);\n }\n\n #terminalHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 16px;\n background-color: #333;\n color: #fff;\n cursor: move;\n }\n\n #terminal-tabs-container {\n display: none;\n align-items: center;\n overflow-x: auto;\n }\n\n .terminal-tabs-list {\n display: flex;\n align-items: center;\n overflow-x: auto;\n }\n\n .terminal-tab {\n font-family: monospace;\n padding: 5px 15px;\n margin-right: 5px;\n cursor: pointer;\n background-color: #444;\n border-radius: 4px 4px 0 0;\n transition: background-color 0.2s ease;\n }\n\n .terminal-tab:hover {\n background-color: #555 !important;\n }\n\n .active-tab {\n background-color: #444 !important;\n font-weight: bold;\n }\n\n #add-terminal-tab {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n margin-left: 5px;\n background-color: #333;\n color: #fff;\n border: 1px solid #555;\n border-radius: 4px;\n cursor: pointer;\n font-size: 18px;\n font-weight: bold;\n }\n\n #login-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n background: #111;\n }\n\n #github-login-button {\n display: flex;\n align-items: center;\n background-color: #24292e;\n color: white;\n border: none;\n border-radius: 6px;\n padding: 10px 16px;\n font-size: 16px;\n cursor: pointer;\n transition: background-color 0.2s;\n }\n\n #github-login-button:hover {\n background-color: #444;\n }\n\n .terminal-container {\n flex: 1;\n overflow: hidden;\n background: #000;\n width: 100%;\n display: none;\n }\n\n .active-container {\n display: block;\n }\n\n #connection-status {\n padding: 5px 10px;\n background: #222;\n color: #ccc;\n font-family: monospace;\n font-size: 12px;\n border-top: 1px solid #444;\n display: none;\n }\n\n #terminalResizeHandle {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 6px;\n cursor: ns-resize;\n background-color: #555;\n opacity: 0.7;\n transition: opacity 0.2s, background-color 0.2s;\n }\n\n #terminalResizeHandle:hover {\n opacity: 1;\n background-color: #888;\n }\n\n #terminalResizeHandle::before {\n content: "";\n position: absolute;\n left: 50%;\n top: 1px;\n transform: translateX(-50%);\n width: 40px;\n height: 3px;\n background-color: #ccc;\n border-radius: 2px;\n }\n\n #user-profile {\n display: flex;\n align-items: center;\n margin-right: 20px;\n }\n\n #user-avatar {\n width: 24px;\n height: 24px;\n border-radius: 50%;\n margin-right: 8px;\n }\n ',document.head.appendChild(e)}createHTML(){const e=document.createElement("div");for(e.innerHTML='\n <button id="terminalToggle" data-turbo-permanent>\n >_\n </button>\n\n <div id="terminalWrapper" data-turbo-permanent>\n <div id="terminalResizeHandle"></div>\n \n <div id="terminalHeader">\n <div id="terminal-tabs-container">\n <div class="terminal-tabs-list">\n <div id="terminal-tab-1" class="terminal-tab active-tab">\n Terminal 1\n </div>\n <button id="add-terminal-tab">+</button>\n </div>\n </div>\n <div id="auth-status">\n Not authenticated\n </div>\n </div>\n \n <div id="login-container">\n <button id="github-login-button">\n <i class="fab fa-github" style="margin-right: 10px; font-size: 20px;"></i>\n Sign in with GitHub\n </button>\n </div>\n \n <div id="terminal-container-1" class="terminal-container active-container"></div>\n \n <div id="connection-status"></div>\n </div>\n ';e.firstChild;)document.body.appendChild(e.firstChild)}setupConfig(){const e=window.location.hostname.replace(/^www\./,""),t="https:"===window.location.protocol,n=t?"wss://":"ws://",o=t?"https://":"http://",i="localhost"===e||e.startsWith("127.0.0.1"),a=["githubClientId","githubAppName"];i||a.push("backendDomain");const r=a.filter((e=>!this.options[e]));if(r.length>0){const e=`WebTerminalEmbed: Missing required options: ${r.join(", ")}`;console.error(e);const t={githubClientId:"Your GitHub OAuth App Client ID",githubAppName:"Your GitHub OAuth App Name"};throw i||(t.backendDomain="The domain running the auth and websocket server"),console.error("Please provide these options when initializing:",t),new Error(e)}const s=this.options.backendDomain;window.CONFIG={GITHUB_CLIENT_ID:this.options.githubClientId,GITHUB_APP_NAME:this.options.githubAppName,VM_TYPE:this.options.vmType||"cka",GITHUB_SCOPE:this.options.githubScope||"read:user",AUTH_SERVER:this.options.authServer||(i?"http://localhost:3000":`${o}auth.${s}`),GITHUB_REDIRECT_URI:this.options.githubRedirectUri||(i?"http://localhost:3000/auth/github/callback":`${o}auth.${s}/auth/github/callback`),WS_URL:this.options.wsUrl||(i?`${n}localhost:8081`:`${n}wss.${s}`),DEFAULT_COLS:this.options.defaultCols||120,DEFAULT_ROWS:this.options.defaultRows||30,DEFAULT_HEIGHT:this.options.defaultHeight||450,MIN_HEIGHT:this.options.minHeight||200,MAX_HEIGHT:this.options.maxHeight||800,MAX_CONNECT_ATTEMPTS:this.options.maxConnectAttempts||3,CONNECT_RETRY_DELAY:this.options.connectRetryDelay||2e3,CONNECTION_TIMEOUT:this.options.connectionTimeout||1e4,TERMINAL_TRANSITION_SPEED:this.options.terminalTransitionSpeed||"0.3s",IS_PRODUCTION:!i,DEBUG:this.options.debug||i,BACKEND_DOMAIN:s},window.CONFIG.DEBUG&&(console.log("Terminal Configuration:",window.CONFIG),i||console.log("Constructed URLs from backend domain:",{websocketUrl:window.CONFIG.WS_URL,authServer:window.CONFIG.AUTH_SERVER,redirectUri:window.CONFIG.GITHUB_REDIRECT_URI}))}loadDependencies(){return new Promise(((e,t)=>{const n=[{type:"css",url:"https://cdn.jsdelivr.net/npm/xterm@5.1.0/css/xterm.min.css"},{type:"css",url:"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"},{type:"js",url:"https://cdn.jsdelivr.net/npm/xterm@5.1.0/lib/xterm.min.js"},{type:"js",url:"https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.7.0/lib/xterm-addon-fit.min.js"}];let o=0;const i=n.length;n.forEach((n=>{if("css"===n.type){const a=document.createElement("link");a.rel="stylesheet",a.href=n.url,a.onload=()=>{o++,o===i&&e()},a.onerror=t,document.head.appendChild(a)}else if("js"===n.type){const a=document.createElement("script");a.src=n.url,a.onload=()=>{o++,o===i&&e()},a.onerror=t,document.head.appendChild(a)}}))}))}terminalLogic(){const e={terminals:{},fitAddons:{},websockets:{},isTerminalVisible:!1,activeTabId:"1",auth:{isAuthenticated:!1,token:null,userProfile:null}};let t=!1;const n={formatDate:e=>`${e.getHours().toString().padStart(2,"0")}:${e.getMinutes().toString().padStart(2,"0")}`,log(t,n="info",o=e.activeTabId){window.CONFIG.DEBUG&&console.log(`[Tab ${o}] ${t}`),"error"!==n&&"success"!==n||this.updateStatusPanel(t,n)},updateStatusPanel(e,t="info"){const n=this.getElement("connection-status");if(!n)return;let o="#ccc";"error"===t&&(o="#ff5555"),"success"===t&&(o="#55ff55"),"info"===t&&(o="#5555ff"),n.innerHTML=`<span style="color: ${o}">${e}</span>`,n.style.display="block"},getElement(e,t=!1){const n=document.getElementById(e);return!n&&t&&console.error(`Required element not found: ${e}`),n},generateUniqueId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2)},o={getAuthTokenFromCookie(){const e=document.cookie.split(";");for(let t of e){const[e,n]=t.trim().split("=");if("auth_token"===e)return n}return null},clearAuthData(){localStorage.removeItem("terminal_auth_token"),localStorage.removeItem("terminal_user_profile"),localStorage.removeItem("github_oauth_state"),document.cookie="auth_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;",e.auth.token=null,e.auth.userProfile=null,e.auth.isAuthenticated=!1,this.updateAuthUI(),n.log("Session expired. Authentication cleared.","error")},initiateGitHubLogin(){const e=Math.random().toString(36).substring(2,15),t=window.CONFIG.GITHUB_APP_NAME,n=`${e}_${t}`;localStorage.setItem("github_oauth_state",n);const o=`https://github.com/login/oauth/authorize?client_id=${window.CONFIG.GITHUB_CLIENT_ID}&redirect_uri=${encodeURIComponent(window.CONFIG.GITHUB_REDIRECT_URI)}&scope=${encodeURIComponent(window.CONFIG.GITHUB_SCOPE)}&state=${n}`;window.CONFIG.DEBUG&&(console.log("Initiating GitHub OAuth flow"),console.log("GitHub App Name:",t),console.log("Full OAuth State:",n),console.log("GitHub Authorization URL:",o)),window.location.href=o},handleOAuthCallback(){"true"===new URLSearchParams(window.location.search).get("auth_success")&&(window.history.replaceState({},document.title,window.location.pathname),sessionStorage.setItem("from_github_login","true"),this.validateTokenFromCookie())},validateTokenFromCookie(){const t=this.getAuthTokenFromCookie();if(!t)return console.warn("No auth token cookie found"),void this.updateAuthStatus("No authentication token found","error");this.updateAuthStatus("Validating token...","info"),fetch(`${window.CONFIG.AUTH_SERVER}/validate-token`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include"}).then((e=>{if(!e.ok)throw new Error(`HTTP error! Status: ${e.status}`);return e.json()})).then((n=>{if(n.valid&&n.user){const o={login:n.user.username,name:n.user.username,avatar_url:"https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"};this.setAuthenticatedState(t,o),r.showTerminal(),a.initializeAndConnectTerminal(e.activeTabId)}else this.updateAuthStatus("Token validation failed","error")})).catch((e=>{console.error("Token Validation Error:",e),this.updateAuthStatus("Error validating token","error")}))},setAuthenticatedState(t,n){return n&&n.login&&""!==n.login?(e.auth.token=t,e.auth.userProfile=n,e.auth.isAuthenticated=!0,localStorage.setItem("terminal_auth_token",t),localStorage.setItem("terminal_user_profile",JSON.stringify(n)),this.updateAuthUI(),!0):(console.error("Cannot authenticate with invalid profile"),this.updateAuthStatus("Authentication failed: Invalid user profile","error"),!1)},updateAuthStatus(e,t="info"){const o=n.getElement("auth-status");if(!o)return;let i="#ccc";"error"===t&&(i="#ff5555"),"success"===t&&(i="#55ff55"),"info"===t&&(i="#5555ff"),o.innerText=e,o.style.color=i},updateAuthUI(){const t=n.getElement("auth-status");t&&(e.auth.isAuthenticated&&e.auth.userProfile?(t.innerHTML=`\n <div id="user-profile">\n <img src="${e.auth.userProfile.avatar_url}" alt="${e.auth.userProfile.login}" \n width="24" height="24" style="border-radius: 50%; vertical-align: middle;" />\n <span>${e.auth.userProfile.name||e.auth.userProfile.login}</span>\n </div>\n `,t.style.marginLeft="auto",t.style.marginRight="10px",t.style.order="3"):(t.innerText="Not authenticated",t.style.color="#ff5555"))},checkExistingAuth(){const t=localStorage.getItem("terminal_auth_token"),n=localStorage.getItem("terminal_user_profile"),o=this.getAuthTokenFromCookie();if(n&&(t||o))try{const i=JSON.parse(n);if(i&&i.login&&""!==i.login){console.log("Found valid profile in storage:",i.login);const n=t||o;return e.auth.token=n,e.auth.userProfile=i,e.auth.isAuthenticated=!0,this.updateAuthUI(),!0}}catch(e){console.error("Error parsing stored profile:",e)}return!(!o||t)&&(this.validateTokenFromCookie(),!1)}},i={connectWebSocket:(t,a=!1)=>new Promise(((s,l)=>{if(e.websockets[t])try{e.websockets[t].close()}catch(e){console.error(`Error closing existing websocket for tab ${t}:`,e)}let c=o.getAuthTokenFromCookie();if(c||(c=localStorage.getItem("terminal_auth_token")),!c)return n.log("Cannot connect: Missing authentication token","error"),void l(new Error("Missing authentication token"));e.auth.userProfile;const d="terminal_"+t+"_"+n.generateUniqueId();let u=`${window.CONFIG.WS_URL}?token=${encodeURIComponent(c)}&terminalid=${encodeURIComponent(d)}&vmtype=${window.CONFIG.VM_TYPE}`;a&&(u+="&checksession=true");try{e.websockets[t]=new WebSocket(u);const o=setTimeout((function(){e.websockets[t]&&e.websockets[t].readyState!==WebSocket.OPEN&&(n.log("Connection timeout. Server not responding.","error"),e.websockets[t]&&e.websockets[t].close(),l(new Error("Connection timeout")))}),window.CONFIG.CONNECTION_TIMEOUT);e.websockets[t].onopen=function(){clearTimeout(o),n.log("WebSocket connection established","info"),e.websockets[t].send(JSON.stringify({type:"auth"})),s()},e.websockets[t].onmessage=function(e){i.handleWebSocketMessage(e,t)},e.websockets[t].onerror=function(e){n.log("websocket received an error",e),clearTimeout(o),n.log(`WebSocket error: ${e.message||"Unknown error"}`,"error"),l(e)},e.websockets[t].onclose=function(i){clearTimeout(o),i.wasClean?n.log(`Connection closed cleanly, code: ${i.code}`,"info"):(n.log("Connection died unexpectedly","error"),r.showConnectionFailurePrompt(t)),e.websockets[t].readyState!==WebSocket.OPEN&&l(new Error("Connection closed"))}}catch(e){n.log(`Error creating WebSocket: ${e.message}`,"error"),l(e)}})),handleWebSocketMessage(t,i){try{let s=JSON.parse(t.data);switch(s.type){case"vm_creating":r.showTerminalLoading(i,"Creating your environment..."),n.log("Your environment is being created. Please wait...","info"),r.setAddTerminalButtonState(!1);break;case"vm_ready":n.log("Your environment is ready!","success");const t=n.getElement("terminal-tabs-container");t&&e.auth.isAuthenticated&&(t.style.display="flex"),r.setAddTerminalButtonState(!0),e.terminals[i]||a.initializeTerminal(i);break;case"connected":n.log("SSH connection established","success");break;case"data":e.terminals[i]&&e.terminals[i].write(s.data);break;case"error":if(n.log(`Error: ${s.message}`,"error"),r.setAddTerminalButtonState(!1),s.message&&s.message.includes("Token expired")&&(console.log("Token expired"),o.clearAuthData(),setTimeout((()=>{Object.keys(e.websockets).forEach((t=>{e.websockets[t]&&(e.websockets[t].onclose=null)})),this.closeAllConnections()}),200)),s.message&&s.message.includes("You have reached the maximum limit")&&(console.log("Max number of terminal reached"),r.showTerminalQuotaMessage(i)),s.cooldown){console.log("Cooldown received - showing message");const t=s.cooldown.formattedTime||n.formatDate(new Date(s.cooldown.expiryTimestamp));r.showCooldownMessage(i,t),s.message&&s.message.includes("session expired")&&o.clearAuthData(),setTimeout((()=>{Object.keys(e.websockets).forEach((t=>{e.websockets[t]&&(e.websockets[t].onclose=null)})),this.closeAllConnections()}),200)}break;case"environment_terminated":if(console.log("Environment terminated - showing cooldown message"),r.setAddTerminalButtonState(!1),s.cooldown){const t=s.cooldown.formattedTime||n.formatDate(new Date(s.cooldown.expiryTimestamp));r.showCooldownMessage(i,t),setTimeout((()=>{Object.keys(e.websockets).forEach((t=>{e.websockets[t]&&(e.websockets[t].onclose=null)})),this.closeAllConnections()}),200)}break;case"closed":n.log("SSH connection closed","info")}}catch(n){e.terminals[i]&&e.terminals[i].write(t.data)}},closeAllConnections(){Object.keys(e.websockets).forEach((t=>{if(e.websockets[t]){try{const n=e.websockets[t];n.onclose=null,n.close()}catch(e){console.error(`Error closing websocket for tab ${t}:`,e)}e.websockets[t]=null}}))}},a={createNewTerminalTab(){const e=document.querySelector(".terminal-tabs-list"),t=e.querySelectorAll(".terminal-tab").length;if(t>=4)return void n.log("Maximum number of terminals reached (4)","error");const o=(t+1).toString(),i=document.createElement("div");i.id=`terminal-tab-${o}`,i.className="terminal-tab",i.style.fontFamily="monospace",i.style.padding="5px 15px",i.style.marginRight="5px",i.style.cursor="pointer",i.style.backgroundColor="#222",i.style.borderRadius="4px 4px 0 0",i.textContent=`Terminal ${o}`;const a=document.getElementById("add-terminal-tab");e.insertBefore(i,a);const s=JSON.parse(localStorage.getItem("terminal_active_tabs")||'["1"]');s.includes(o)||(s.push(o),localStorage.setItem("terminal_active_tabs",JSON.stringify(s))),this.createTerminalContainer(o),i.addEventListener("click",(function(){r.switchTab(o)})),r.switchTab(o)},createTerminalContainer(e){if(document.getElementById(`terminal-container-${e}`))return;const t=document.createElement("div");t.id=`terminal-container-${e}`,t.className="terminal-container",t.style.flex="1",t.style.overflow="hidden",t.style.background="#000",t.style.width="100%",t.style.display="none";const o=n.getElement("terminalWrapper"),i=n.getElement("connection-status");o&&i&&o.insertBefore(t,i)},initializeTerminal(t){if(console.log(`Initializing terminal for tab ${t}...`),e.auth.isAuthenticated){var o=n.getElement(`terminal-container-${t}`);if(o){if(o.innerHTML="",e.terminals[t]){try{e.terminals[t].dispose()}catch(e){console.error(`Error disposing terminal for tab ${t}:`,e)}e.terminals[t]=null,e.fitAddons[t]=null}e.terminals[t]=new Terminal({cursorBlink:!0,theme:{background:"#000",foreground:"#f0f0f0"},fontFamily:"monospace",fontSize:14,cols:window.CONFIG.DEFAULT_COLS,rows:window.CONFIG.DEFAULT_ROWS,scrollback:1e3,convertEol:!0,disableStdin:!1}),"undefined"!=typeof FitAddon&&void 0!==FitAddon.FitAddon&&(e.fitAddons[t]=new FitAddon.FitAddon,e.terminals[t].loadAddon(e.fitAddons[t])),e.terminals[t].open(o),setTimeout((()=>this.resizeTerminal(t)),100),this.setupTerminalDataHandler(t)}else console.error(`Terminal container for tab ${t} not found`)}else console.error("Cannot initialize terminal: Not authenticated")},setupTerminalDataHandler(t){e.terminals[t]&&e.websockets[t]&&e.terminals[t].onData((function(n){e.websockets[t]&&e.websockets[t].readyState===WebSocket.OPEN&&e.websockets[t].send(JSON.stringify({type:"data",data:n}))}))},initializeAndConnectTerminal(n,o=!1){t||(t=!0,e.auth.isAuthenticated?(r.showTerminalLoading(n),i.connectWebSocket(n,o).finally((()=>{setTimeout((()=>{t=!1}),1e3)}))):console.error("Cannot connect: Not authenticated"))},resizeTerminal(t){if(e.terminals[t]&&e.fitAddons[t])try{if(e.fitAddons[t].fit(),e.websockets[t]&&e.websockets[t].readyState===WebSocket.OPEN){const n=Math.max(e.terminals[t].cols,window.CONFIG.DEFAULT_COLS),o=Math.max(e.terminals[t].rows,window.CONFIG.DEFAULT_ROWS);e.websockets[t].send(JSON.stringify({type:"resize",cols:n,rows:o}))}}catch(e){console.error(`Error resizing terminal ${t}:`,e)}}},r={showTerminal(){e.isTerminalVisible=!0;const t=n.getElement("terminalWrapper");if(!t)return;t.style.display="flex",t.style.bottom="0px";const o=n.getElement("login-container"),i=n.getElement("terminal-tabs-container"),r=n.getElement(`terminal-container-${e.activeTabId}`);if(e.auth.isAuthenticated){o&&(o.style.display="none"),i&&(i.style.display="flex"),r&&(r.style.display="block");e.websockets[e.activeTabId]&&e.websockets[e.activeTabId].readyState===WebSocket.OPEN||e.terminals[e.activeTabId]&&e.terminals[e.activeTabId].rows>0?setTimeout((()=>a.resizeTerminal(e.activeTabId)),50):a.initializeAndConnectTerminal(e.activeTabId)}else o&&(o.style.display="flex"),i&&(i.style.display="none"),r&&(r.style.display="none")},hideTerminal(){e.isTerminalVisible=!1;const t=n.getElement("terminalWrapper");t&&(t.style.bottom="-"+t.offsetHeight+"px")},toggleTerminal(){e.isTerminalVisible?this.hideTerminal():this.showTerminal()},switchTab(t){if(!e.auth.isAuthenticated)return;e.activeTabId=t,document.querySelectorAll(".terminal-tab").forEach((function(e){e.classList.remove("active-tab"),e.style.backgroundColor="#222"}));const o=n.getElement(`terminal-tab-${t}`);o&&(o.classList.add("active-tab"),o.style.backgroundColor="#444"),document.querySelectorAll(".terminal-container").forEach((function(e){e.style.display="none"}));const i=n.getElement(`terminal-container-${t}`);i&&(i.style.display="block"),e.terminals[t]?setTimeout((()=>a.resizeTerminal(t)),50):e.websockets[t]&&e.websockets[t].readyState===WebSocket.OPEN?a.initializeTerminal(t):a.initializeAndConnectTerminal(t)},setAddTerminalButtonState(e){const t=n.getElement("add-terminal-tab");t&&(e?(t.style.opacity="1",t.style.cursor="pointer",t.style.backgroundColor="#333",t.style.borderColor="#555",t.disabled=!1):(t.style.opacity="0.5",t.style.cursor="not-allowed",t.style.backgroundColor="#222",t.style.borderColor="#444",t.disabled=!0))},showTerminalLoading(e,t="Connecting to your environment..."){const o=n.getElement(`terminal-container-${e}`);o&&(o.innerHTML=`\n <div style="\n position: absolute; \n top: 50%; \n left: 50%; \n transform: translate(-50%, -50%);\n color: #5555ff;\n text-align: center;\n font-family: monospace;\n font-size: 18px;\n ">\n ${t}<br>\n <span style="font-size: 14px; color: #aaa; margin-top: 10px; display: block;">\n Please wait\n </span>\n </div>\n `)},showCooldownMessage(t,o){const i=n.getElement(`terminal-container-${t}`);if(!i)return;const a=n.getElement("terminal-tabs-container");if(a&&(a.style.display="none"),e.terminals[t]){try{e.terminals[t].dispose()}catch(e){console.error(`Error disposing terminal: ${e}`)}e.terminals[t]=null}i.innerHTML=`\n <div style="\n position: absolute; \n top: 50%; \n left: 50%; \n transform: translate(-50%, -50%);\n color: #ff5555;\n text-align: center;\n font-family: monospace;\n font-size: 18px;\n background-color: rgba(0, 0, 0, 0.7);\n padding: 20px;\n border-radius: 8px;\n width: 80%;\n max-width: 500px;\n ">\n The session has expired<br>\n <span style="font-size: 14px; color: #ffffff; margin-top: 10px; display: block;">\n Reload the page after ${o} to request a new session\n </span>\n </div>\n `},showReconnectUI(e){const t=n.getElement(`terminal-container-${e}`);if(!t)return;const o=n.getElement("terminal-tabs-container");o&&(o.style.display="none"),t.innerHTML='\n <div style="\n position: absolute; \n top: 50%; \n left: 50%; \n transform: translate(-50%, -50%);\n color: #5555ff;\n text-align: center;\n font-family: monospace;\n font-size: 18px;\n ">\n Ready to Connect<br>\n <span style="font-size: 14px; color: #aaa; margin-top: 10px; display: block;">\n <button id="reconnect-button" style="margin-top: 15px; padding: 5px 10px; \n background: #444; border: 1px solid #666; color: white; \n cursor: pointer; border-radius: 3px;">\n You can now request a new session\n </button>\n </span>\n </div>\n ';const i=n.getElement("reconnect-button");i&&i.addEventListener("click",(function(){o&&(o.style.display="flex"),a.initializeAndConnectTerminal(e)}))},showTerminalQuotaMessage(e){const t=n.getElement(`terminal-container-${e}`);t&&(t.innerHTML='\n <div style="\n position: absolute; \n top: 50%; \n left: 50%; \n transform: translate(-50%, -50%);\n color: #ff5555;\n text-align: center;\n font-family: monospace;\n font-size: 18px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 20px;\n border-radius: 8px;\n width: 80%;\n max-width: 500px;\n ">\n You reached the maximal number of terminals<br>\n <span style="font-size: 14px; color: #ffffff; margin-top: 10px; display: block;">\n Are you using multiple browsers simultaneously ?\n </span>\n </div>\n ')},showConnectionFailurePrompt(e){const t=n.getElement(`terminal-container-${e}`);if(!t)return;o.clearAuthData();const i=n.getElement("terminal-tabs-container");i&&(i.style.display="none"),t.innerHTML='\n <div style="\n position: absolute; \n top: 50%; \n left: 50%; \n transform: translate(-50%, -50%);\n color: #ff5555;\n text-align: center;\n font-family: monospace;\n font-size: 18px;\n background-color: rgba(0, 0, 0, 0.85);\n padding: 20px;\n border-radius: 8px;\n width: 80%;\n max-width: 500px;\n ">\n Connection to the server has been lost<br>\n <span style="font-size: 14px; color: #ffffff; margin-top: 10px; display: block;">\n Please reload the page to reconnect\n </span>\n </div>\n '},setupVerticalResize(t,n){if(!t||!n)return;let o=!1,i=0,r=0;t.addEventListener("mousedown",(function(e){o=!0,i=e.clientY,r=n.offsetHeight,n.style.transition="none",document.body.style.userSelect="none"})),document.addEventListener("mousemove",(function(t){if(!o)return;const a=i-t.clientY,s=Math.min(Math.max(r+a,window.CONFIG.MIN_HEIGHT),CONFIG.MAX_HEIGHT);n.style.height=s+"px",e.isTerminalVisible&&(n.style.bottom="0px"),Object.keys(e.terminals).forEach((t=>{e.terminals[t]&&e.fitAddons[t]&&e.fitAddons[t].fit()}))})),document.addEventListener("mouseup",(function(){o&&(o=!1,n.style.transition=`bottom ${window.CONFIG.TERMINAL_TRANSITION_SPEED} ease-in-out`,document.body.style.userSelect="",e.isTerminalVisible||(n.style.bottom="-"+n.offsetHeight+"px"),Object.keys(e.terminals).forEach((t=>{e.terminals[t]&&e.fitAddons[t]&&a.resizeTerminal(t)})))}))},setupHorizontalDrag(e,t){if(!e||!t)return;let n=!1,o=0;e.addEventListener("mousedown",(function(i){(i.target===e||"BUTTON"!==i.target.tagName&&!i.target.classList.contains("terminal-tab"))&&(n=!0,o=i.clientX-t.getBoundingClientRect().left,t.style.transition="none",document.body.style.userSelect="none")})),document.addEventListener("mousemove",(function(e){if(!n)return;const i=e.clientX-o,a=window.innerWidth-t.offsetWidth;i>=0&&i<=a&&(t.style.left=i+"px")})),document.addEventListener("mouseup",(function(){n&&(n=!1,t.style.transition="left 0.3s ease-in-out",document.body.style.userSelect="")}))}};!function(){const t=o.checkExistingAuth();if(o.handleOAuthCallback(),n.getElement("terminal-container-1")||a.createTerminalContainer("1"),r.setAddTerminalButtonState(!1),function(){const t={toggleButton:n.getElement("terminalToggle"),wrapper:n.getElement("terminalWrapper"),minimizeButton:n.getElement("terminalMinimize"),closeButton:n.getElement("terminalClose"),resizeHandle:n.getElement("terminalResizeHandle"),header:n.getElement("terminalHeader"),githubLoginButton:n.getElement("github-login-button"),tab1:n.getElement("terminal-tab-1"),tab2:n.getElement("terminal-tab-2")};t.toggleButton&&t.toggleButton.addEventListener("click",(()=>r.toggleTerminal()));t.minimizeButton&&t.minimizeButton.addEventListener("click",(()=>r.toggleTerminal()));t.closeButton&&t.closeButton.addEventListener("click",(function(){i.closeAllConnections(),r.hideTerminal()}));t.githubLoginButton&&t.githubLoginButton.addEventListener("click",(()=>o.initiateGitHubLogin()));const s=n.getElement("add-terminal-tab");s&&s.addEventListener("click",(function(){s.disabled||(e.auth.isAuthenticated?a.createNewTerminalTab():n.log("Please login first","error"))}));t.tab1&&t.tab1.addEventListener("click",(function(){r.switchTab("1")}));t.tab2&&t.tab2.addEventListener("click",(function(){r.switchTab("2")}));t.resizeHandle&&t.wrapper&&r.setupVerticalResize(t.resizeHandle,t.wrapper);t.header&&t.wrapper&&r.setupHorizontalDrag(t.header,t.wrapper);document.addEventListener("keydown",(function(e){e.altKey&&"t"===e.key&&(r.toggleTerminal(),e.preventDefault())}))}(),window.addEventListener("resize",(function(){e.isTerminalVisible&&Object.keys(e.terminals).forEach((t=>{e.terminals[t]&&e.fitAddons[t]&&a.resizeTerminal(t)}))})),window.addEventListener("beforeunload",(function(){i.closeAllConnections(),localStorage.removeItem("terminal_active_tabs")})),t)setTimeout((()=>r.showTerminal()),500);else{const e=n.getElement("login-container");e&&(e.style.display="block")}}()}destroy(){["terminalWrapper","terminalToggle","web-terminal-styles"].forEach((e=>{const t=document.getElementById(e);t&&t.remove()}))}}"undefined"!=typeof module&&module.exports?module.exports=t:"function"==typeof define&&define.amd?define([],(function(){return t})):e.WebTerminalEmbed=t}("undefined"!=typeof window?window:this);