Skip to content

Live Demo


View fullpage demo

My First Terminal

Code
css
@import url('https://unpkg.com/xterminal/dist/xterminal.css');

.error {
    color: rgb(248, 88, 88);
}

.spinner:after {
    animation: changeContent 0.8s linear infinite;
    content: "⠋";
}

@keyframes changeContent {
    10% { content: "⠙"; }
    20% { content: "⠹"; }
    30% { content: "⠸"; }
    40% { content: "⠼"; }
    50% { content: "⠴"; }
    60% { content: "⠦"; }
    70% { content: "⠧"; }
    80% { content: "⠇"; }
    90% { content: "⠏"; }
}
html
<link rel="stylesheet" href="styles.css">

<div id="app"></div>

<script src="https://unpkg.com/xterminal/dist/xterminal.umd.js"></script>

<script src="createShell.js"></script>
<script src="createTerminal.js"></script>

<script>
    window.onload = () => createTerminal('#app');
</script>
js
function createTerminal(target) {
    const term = new XTerminal({ target });

    const state = {
        username: "user",
        hostname: "web"
    };

    // input evaluator
    const shell = createShell();

    // print prompt and get ready for user input
    function promptUser() {
        term.write(`┌[${state.username}@${state.hostname}]\n`);
        term.write("└$ ");
        term.resume();
        term.focus();
    }

    // user input handler
    term.on("data", async (input) => {
        // deactivate until the execution is done
        term.pause();

        // execute command
        await shell
            .execute(term, input)
            .then((res) => res && term.writeln(res))
            .catch((err) => {
                if (err) {
                    // sanitize error to prevent xss attacks
                    // error may contain user input or HTML strings (like script tags)
                    term.writeln(
                        `<span class="error">${XTerminal.escapeHTML(err)}</span>\n`
                    );
                }
            })
            .finally(promptUser);
    });

    // greeting message
    term.writeln("Welcome to XTerminal (v" + XTerminal.version + ")");
    term.writeln("Type `help` for available commands\n");

    // kickstart
    promptUser();

    // remember to free resources
    window.addEventListener("unload", () => term.dispose());
}
js

/**
 * Escape HTML special characters to prevent XSS attacks
 */
function escapeHTML(text) {
    const tmp = document.createElement("div");
    tmp.textContent = text;
    return tmp.innerHTML;
}

function createShell() {
    // Help
    const manual = `XTerminal : version ${XTerminal.version}

Type 'help' to see this list

Commands:

  gh (username)   search for github users
  js [expr]       execute a JS expression
  clear           clear the terminal screen
  help            display this list
`;

    // Get public github user information
    async function fetchGitHubUser(username) {
        return fetch("https://api.github.com/users/" + username)
            .then((res) => res.json())
            .then((res) => {
                const escapedName = escapeHTML(res.name);
                const escapedBio = escapeHTML(res.bio);
                const escapedRepos = escapeHTML(res.public_repos);
                const escapedAvatar = escapeHTML(res.avatar_url);
                return (
                    '<table border="0">' +
                    "<tr>" +
                    `<td rowspan="3" width="100"><img width="75" src="${escapedAvatar}" alt="${escapedName}" /></td>` +
                    `<td>Name</td>` +
                    `<td>${escapedName}</td>` +
                    "</tr>" +
                    "<tr>" +
                    `<td>Bio</td>` +
                    `<td>${escapedBio}</td>` +
                    "</tr>" +
                    "<tr>" +
                    `<td>Repos</td>` +
                    `<td>${escapedRepos}</td>` +
                    "</tr>" +
                    "</table>"
                );
            });
    }

    // evaluate user input from the terminal
    // -> can be shared among several terminal objects
    function execute(term, command = "") {
        let args = command.split(" ");
        let cmd = args.shift();
        // GitHub User Search
        if (cmd == "gh") {
            return new Promise(async (res, rej) => {
                let output, error;
                term.write('<span class="spinner"></span> Searching...');
                await fetchGitHubUser(args.join(""))
                    .then((val) => (output = val))
                    .catch((err) => (error = ":( Not found!"))
                    .finally(() => term.clearLast());
                if (error) rej(error);
                else res(output);
            });
        }
        // JavaScript Evaluation
        else if (cmd == "js") {
            return new Promise((res, rej) => {
                try {
                    let output = eval(args.join(" ")) + "\n";
                    res(output);
                } catch (error) {
                    rej(error);
                }
            });
        }
        // Help menu
        else if (cmd == "help") {
            return Promise.resolve(manual);
        }
        // Clear the terminal
        else if (cmd == "clear") {
            term.clear();
            return Promise.resolve(null);
        }
        // Oopps!
        else {
            return Promise.reject(`sh: '${cmd}' command not found`);
        }
    }

    return { execute };
}