Como usuário do Google Chrome, você provavelmente já usou algumas extensões nesse navegador. Você já se perguntou como elas são criadas, ou se você poderia criar uma?

Este artigo orienta você no processo de criação de uma extensão do Chrome, especificamente uma que usa o React e a API da Kinsta para gerenciar plugins em sites WordPress hospedados na Kinsta.

O que é uma extensão do Chrome?

Uma extensão do Chrome é um programa instalado no navegador Chrome que aprimora sua funcionalidade. As extensões podem variar de simples botões de ícones na barra de ferramentas a recursos totalmente integrados que interagem profundamente com sua experiência de navegação.

Como criar uma extensão do Chrome

A criação de uma extensão do Chrome é semelhante ao desenvolvimento de um aplicativo web, mas requer um arquivo formatado em JSON chamado manifest.json. Esse arquivo funciona como a espinha dorsal da extensão, determinando suas configurações, as permissões e as funcionalidades que você deseja incluir.

Para começar, crie uma pasta que conterá todos os arquivos da extensão. Em seguida, crie um arquivo manifest.json na pasta.

Um arquivo manifest.json básico para uma extensão do Chrome inclui as principais propriedades que definem as configurações básicas da extensão. Abaixo está um exemplo de um arquivo manifest.json que inclui os campos necessários para funcionar:

{
  "manifest_version": 3,
  "name": "My Chrome extension",
  "version": "1.0",
  "description": "Here is a description for my Chrome extension."
}

Você pode carregar e testar isso como uma extensão descompactada do Chrome. Navegue até chrome://extensions em seu navegador, alterne para o Developer mode e clique no botão Load Unpacked (Carregar descompactado). Isso abrirá um navegador de arquivos, e você poderá selecionar o diretório que criou para a sua extensão.

Carregue uma extensão do Chrome clicando em Load unpacked no modo de desenvolvedor.
Carregue uma extensão do Chrome clicando em Load unpacked no modo de desenvolvedor.

Quando você clicar no ícone da extensão, nada acontecerá, porque você não criou uma interface de usuário.

Crie uma interface de usuário (pop-up) para sua extensão do Chrome

Como em todo aplicativo web, a interface do usuário (IU) da sua extensão usa HTML para estruturar o conteúdo, CSS para estilizá-lo e JavaScript para adicionar interatividade.

Vamos criar uma IU básica usando todos esses arquivos. Comece criando um arquivo HTML (popup.html). Esse arquivo define a estrutura dos elementos da sua interface do usuário, como texto, cabeçalhos, imagens e botões. Adicione o seguinte código:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Hello World</title>
        <link rel="stylesheet" href="popup.css" />
    </head>
    <body>
        <h1>Hello World!</h1>
        <p>My first Chrome Extension</p>
        <button> id="sayHello">Say Hello</button>
        <script> src="popup.js"></script>
    </body>
</html>

O código acima cria um título, um parágrafo e um botão. Os arquivos CSS e JavaScript também estão vinculados. Agora, adicione alguns estilos no arquivo popup.css:

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: Arial, sans-serif;
    background-color: aliceblue;
    padding: 20px;
}

Em seguida, no arquivo popup.js, adicione um ouvinte de eventos ao botão para que, quando ele for clicado, seja exibido um alerta:

const sayHelloBtn = document.getElementById('sayHello');
sayHelloBtn.addEventListener('click', async () => {
    let tab = await chrome.tabs.query({ active: true });
    chrome.scripting.executeScript({
        target: { tabId: tab[0].id },
        function: () => alert('Hello from the extension!'),
    });
});

Esse código JavaScript recupera a aba ativa atual e usa a API de scripts do Chrome para executar um script que exibe um alerta com uma mensagem de saudação quando o botão Say Hello é clicado. Isso introduz uma interatividade básica em sua extensão do Chrome.

Com essas etapas, você configurou uma interface de usuário pop-up simples para sua extensão do Chrome que inclui texto, estilo e funcionalidade básicos.

Por fim, você precisa ativar o arquivo pop-up no arquivo manifest.json adicionando algumas permissões:

{
    . . . ,
    "action": {
        "default_popup": "popup.html"
    },
    "permissions": [
        "scripting",
        "tabs"
    ],
    "host_permissions": [
        "http://*/*",
        "https://*/*"
    ]
}

Na configuração acima, a chave default_popup especifica que popup.html será a interface do usuário (UI) padrão quando o usuário interagir com a extensão. O array permissions inclui scripting e tabs, essenciais para que a extensão interaja com as abas e use os recursos de script do navegador.

O array host_permissions especifica com quais sites sua extensão pode interagir. Os padrões http://*/* e https://*/* indicam que a sua extensão pode interagir com todos os sites acessados pelos protocolos HTTP e HTTPS.

Com essas configurações em seu arquivo manifest.json, sua extensão do Chrome está configurada corretamente para exibir um pop-up e executar scripts.

Recarregue sua extensão do Chrome

Com essas alterações efetuadas em sua pasta local, você precisa atualizar a pasta descompactada carregada no Chrome. Para fazer isso, abra a página de extensões do Chrome, localize sua extensão e clique no ícone de recarregar.

Clique no ícone de atualização para recarregar a extensão.
Clique no ícone de atualização para recarregar a extensão.

Em seguida, você pode clicar no ícone da extensão, e uma janela pop-up será exibida. Quando você clicar no botão Say Hello, um alerta será exibido.

Agora você tem um conhecimento básico de como começar a criar uma extensão do Chrome. E dá pra fazer muito mais. Você pode manipular a interface do usuário do seu site, fazer solicitações de API, recuperar dados de URLs para executar operações específicas, e muito mais.

Como criar uma extensão do Chrome com o React

Como mencionamos anteriormente, a criação de uma extensão do Chrome é semelhante à criação de um aplicativo web. Você pode usar frameworks web populares como o React.

Para o React, o arquivo manifest.json é criado na pasta public. Essa pasta é usada para ativos estáticos que você não deseja que sejam processados pelo Webpack (ou pacotes semelhantes que o React pode usar internamente em ferramentas como Create React App).

Quando você cria seu aplicativo React, o processo de build copia todo o conteúdo da pasta public para a pasta dist. Eis como você pode criar uma extensão do Chrome com o React:

  1. Crie um novo aplicativo React. Você pode usar o ambiente de desenvolvimento local Vite executando o seguinte comando em seu terminal:
npm create vite@latest

Em seguida, dê um nome ao seu projeto e selecione React como framework. Feito isso, navegue até a pasta do projeto e instale as dependências:

cd <project-name>
npm install
  1. Na pasta public do seu projeto React, crie um arquivo manifest.json. Adicione as seguintes configurações:
{
    "manifest_version": 3,
    "name": "React Chrome extension",
    "description": "Chrome extension built with React",
    "version": "0.1.0",
    "action": {
        "default_popup": "index.html"
    },
    "permissions": [
        "tabs"
    ],
    "host_permissions": [
        "http://*/*",
        "https://*/*"
    ]
}

A configuração de uma extensão do Chrome inclui um objeto action que define index.html como o pop-up padrão quando você clica no ícone da extensão. Esse é o arquivo HTML estático gerado quando você cria seu aplicativo React.

  1. Desenvolva o aplicativo React. Sinta-se à vontade para fazer solicitações de API, estilizá-las como desejar, usar hooks do React, e mais.
  1. Quando você terminar de criar a interface do usuário da extensão, execute o comando de build no React (npm run build). Todos os seus ativos, inclusive o arquivo manifest.json, o index.html gerado pelo React, e outros, são movidos para a pasta dist ou build.
  2. Por fim, carregue sua extensão no Chrome. Navegue até chrome://extensions/ e recarregue sua extensão.

Criando uma extensão do Chrome para gerenciar os plugins do seu site com a API da Kinsta

Esta é a aparência da extensão do Chrome que você criará:

Extensão do Chrome criada com React interagindo com a API da Kinsta.
Extensão do Chrome criada com React interagindo com a API da Kinsta.

Quando clicada, a extensão exibe uma lista de sites com plugins desatualizados em sua conta MyKinsta. Você pode ver uma lista dos plugins e clicar no botão View in MyKinsta para navegar até a página Themes & Plugins do site, onde poderá atualizar cada plugin.

Vamos explorar como você pode criar a extensão do Chrome.

Entendendo a API da Kinsta

A API da Kinsta é uma ferramenta poderosa que permite que você interaja de forma programática com os serviços da Kinsta, como sites WordPress hospedados. Pode ajudá-lo a automatizar várias tarefas relacionadas ao gerenciamento de WordPress, incluindo criação de sites, recuperação de informações do site, obter o status de um site, navegação e restauração de backups, e muito mais.

Para usar a API da Kinsta, você deve ter uma conta com pelo menos um site, aplicativo ou banco de dados WordPress no MyKinsta. Você também deve gerar uma chave de API para autenticar e acessar sua conta.

Para gerar uma chave de API:

  1. Vá para o painel MyKinsta.
  2. Navegue até a página Chaves API (Seu nome > Configurações da empresa > Chaves API).
  3. Clique em Criar chave de API.
  4. Escolha uma expiração ou defina uma data de início personalizada e o número de horas para a chave expirar.
  5. Dê à chave um nome exclusivo.
  6. Clique em Gerar.

Depois de criar uma chave de API, copie-a e armazene-a em um local seguro (recomenda-se usar um gerenciador de senhas). Você pode gerar várias chaves API, que serão listadas na página Chaves API. Se você precisar revogar uma chave de API, clique no botão Revogar.

Gerencie os plugins do seu site com a API da Kinsta e o React

Vamos começar desenvolvendo uma interface de usuário em React, que será transformada em uma extensão do Chrome. Este guia pressupõe familiaridade básica com o React e a interação com a API.

Configurando o ambiente

Primeiramente, no arquivo App.jsx, defina uma constante para a URL da API da Kinsta para evitar redundância em seu código:

const KinstaAPIUrl = 'https://api.kinsta.com/v2';

Por segurança, armazene dados confidenciais, como a chave de API e o ID da empresa na Kinsta, em um arquivo .env.local para mantê-los seguros e fora do seu código-fonte:

VITE_KINSTA_COMPANY_ID=YOUR_COMPANY_ID
VITE_KINSTA_API_KEY=YOUR_API_KEY

Obtenha dados com a API da Kinsta

No arquivo App.jsx, você precisa fazer várias solicitações à API da Kinsta para recuperar informações sobre sites e seus plugins.

  1. Recupere sites da empresa: Comece obtendo uma lista de sites associados à conta da sua empresa na Kinsta. Use o ID da empresa em uma solicitação GET, que retorna um array de detalhes do site.
    const getListOfCompanySites = async () => {
          const query = new URLSearchParams({
            company: import.meta.env.VITE_KINSTA_COMPANY_ID,
          }).toString();
          const resp = await fetch(`${KinstaAPIUrl}/sites?${query}`, {
            method: 'GET',
            headers: {
              Authorization: `Bearer ${import.meta.env.VITE_KINSTA_API_KEY}`,
            },
          });
          const data = await resp.json();
          const companySites = data.company.sites;
          return companySites;
        }
  2. Obtenha os dados de ambiente para cada site: Para cada site, recupere os ambientes, que incluem o ID do ambiente necessário para outras solicitações. Isso envolve o mapeamento de cada site e a realização de uma chamada de API para o endpoint /sites/${siteId}/environments.
     const companySites = await getListOfCompanySites();
        // Get all environments for each site
    
        const sitesEnvironmentData = companySites.map(async (site) => {
          const siteId = site.id;
          const resp = await fetch(`${KinstaAPIUrl}/sites/${siteId}/environments`, {
            method: 'GET',
            headers: {
              Authorization: `Bearer ${import.meta.env.VITE_KINSTA_API_KEY}`,
            },
          });
          const data = await resp.json();
          const environments = data.site.environments;
          return {
            id: siteId,
            name: site.display_name,
            environments: environments,
          };
        });
  3. Recupere os plugins para cada ambiente de site: Por fim, use o ID do ambiente para buscar plugins para cada site. Essa etapa envolve uma função de mapeamento e uma chamada de API para o endpoint /sites/environments/${environmentId}/plugins de cada ambiente.
    // Wait for all the promises to resolve
        const sitesData = await Promise.all(sitesEnvironmentData);
    
        // Get all plugins for each environment
        const sitesWithPlugin = sitesData.map(async (site) => {
          const environmentId = site.environments[0].id;
          const resp = await fetch(
            `${KinstaAPIUrl}/sites/environments/${environmentId}/plugins`,
            {
              method: 'GET',
              headers: {
                Authorization: `Bearer ${import.meta.env.VITE_KINSTA_API_KEY}`,
              },
            }
          );
          const data = await resp.json();
          const plugins = data.environment.container_info;
          return {
            env_id: environmentId,
            name: site.name,
            site_id: site.id,
            plugins: plugins,
          };
        });

    Agora você pode reunir todas essas solicitações em uma função usada para retornar o array final de sites com detalhes básicos sobre cada site e seus plugins:

    const getSitesWithPluginData = async () => {
      const getListOfCompanySites = async () => {
        const query = new URLSearchParams({
          company: import.meta.env.VITE_KINSTA_COMPANY_ID,
        }).toString();
        const resp = await fetch(`${KinstaAPIUrl}/sites?${query}`, {
          method: 'GET',
          headers: {
            Authorization: `Bearer ${import.meta.env.VITE_KINSTA_API_KEY}`,
          },
        });
        const data = await resp.json();
        const companySites = data.company.sites;
        return companySites;
      }
    
      const companySites = await getListOfCompanySites();
    
      // Get all environments for each site
      const sitesEnvironmentData = companySites.map(async (site) => {
        const siteId = site.id;
        const resp = await fetch(`${KinstaAPIUrl}/sites/${siteId}/environments`, {
          method: 'GET',
          headers: {
            Authorization: `Bearer ${import.meta.env.VITE_KINSTA_API_KEY}`,
          },
        });
        const data = await resp.json();
        const environments = data.site.environments;
        return {
          id: siteId,
          name: site.display_name,
          environments: environments,
        };
      });
    
      // Wait for all the promises to resolve
      const sitesData = await Promise.all(sitesEnvironmentData);
    
      // Get all plugins for each environment
      const sitesWithPlugin = sitesData.map(async (site) => {
        const environmentId = site.environments[0].id;
        const resp = await fetch(
          `${KinstaAPIUrl}/sites/environments/${environmentId}/plugins`,
          {
            method: 'GET',
            headers: {
              Authorization: `Bearer ${import.meta.env.VITE_KINSTA_API_KEY}`,
            },
          }
        );
        const data = await resp.json();
        const plugins = data.environment.container_info;
        return {
          env_id: environmentId,
          name: site.name,
          site_id: site.id,
          plugins: plugins,
        };
      });
    
      // Wait for all the promises to resolve
      const sitesWithPluginData = await Promise.all(sitesWithPlugin);
      return sitesWithPluginData;
    }

Exibindo dados do site

Crie um estado com o hook useState para armazenar sites com plugins desatualizados. O hook useEffect também chamará o método getSitesWithPluginData() e extrairá os detalhes do site quando o componente for montado.

No hook useEffect, crie uma função que fará um loop em cada site para filtrar os sites com plugins desatualizados e, em seguida, armazená-los no estado:

const [sitesWithOutdatedPlugin, setSitesWithOutdatedPlugin] = useState([]);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
  const checkSitesWithPluginUpdate = async () => {
    const sitesWithPluginData = await getSitesWithPluginData();
    const sitesWithOutdatedPlugin = sitesWithPluginData.map((site) => {
      const plugins = site.plugins.wp_plugins.data;
      const outdatedPlugins = plugins.filter((plugin) => plugin.update === "available");
      if (outdatedPlugins.length > 0) {
        const kinstaDashboardPluginPageURL = `https://my.kinsta.com/sites/plugins/${site.site_id}/${site.env_id}?idCompany=${import.meta.env.VITE_KINSTA_COMPANY_ID}`;
        return {
          name: site.name,
          plugins: outdatedPlugins,
          url: kinstaDashboardPluginPageURL,
        };
      }
    });

    setSitesWithOutdatedPlugin(sitesWithOutdatedPlugin);

  checkSitesWithPluginUpdate();
  setIsLoading(false);
}, []);

No código acima, você percebe que o estado de carregamento também é criado e definido como true por padrão. Este será usado para controlar como os dados são exibidos. Quando todos os dados são carregados, definimos como false.

Abaixo você encontra uma marcação para renderizar os dados do site e os plugins na interface do usuário.

import { useEffect, useState } from "react"
import KinstaLogo from './assets/kinsta-logo.png'
import PluginPage from './components/PluginsPage'

function App() {
  // load the data from the API
  return (
    <div className="container">
        <div>
          <div> className="title-section">
            <img src={KinstaLogo} className="logo" alt="" />
          </div>
          <p> className="info-box">
            Get quick information about your site plugins that need update.
          </p>
          {isLoading ? (
            <p>Loading...</p>
          ) : (
            <>
              <div className="content">
                <p>The following sites have plugins that need to be updated.</p>
                {sitesWithOutdatedPlugin.map((site, index) => {
                  return (
                    <PluginPage key={index} {...site} />
                  );
                })}
              </div>
            </>
          )}
        </div>
    </div>
  )
}
export default App

O código inclui um cabeçalho com uma logo e um parágrafo informativo. O conteúdo da interface do usuário é renderizado condicionalmente com base no estado de isLoading. Se os dados ainda estiverem sendo carregados, será exibida uma mensagem de carregamento. Depois que os dados são carregados, ele apresenta os dados sobre os sites e todos os plugins que exigem atualizações.

Você também notará um componente: PluginPage (PluginPage.jsx). Esse componente foi projetado para exibir sites individuais e os detalhes dos seus plugins. Inclui uma funcionalidade para alternar a visibilidade dos detalhes do plugin.

import { useState } from "react"
import { FaRegEye } from "react-icons/fa";
import { FaRegEyeSlash } from "react-icons/fa";

const PluginUse = (site) => {
    const [viewPlugin, setViewPlugin] = useState(false);

    return (
        <>
            <div className="site-card">
                <div className="site-card-details">
                    <p>{site.name}</p>
                    <div className="both-btns">
                        <a> href={site.url} target="_blank" rel="noreferrer" className="btn">
                            View in MyKinsta
                        </a>
                        <button onClick={() => setViewPlugin(!viewPlugin)} className="btn" title="View Plugins">
                            {viewPlugin ? <FaRegEyeSlash /> : <FaRegEye />}
                        </button>
                    </div>
                </div>
                {viewPlugin && (
                    <div className="plugin-list">
                        {site.plugins.map((plugin, index) => {
                            return (
                                <div key={index} className="plugin-card">
                                    <p>{plugin.name}</p>
                                    <div className="plugin-version-info">
                                        <p>Current Version: {plugin.version}</p>
                                        <p>Latest Version: {plugin.update_version}</p>
                                    </div>
                                </div>
                            );
                        })}
                    </div>
                )}
            </div>
        </>
    )
}
export default PluginUse

Configure o arquivo manifest

Para transformar sua interface de usuário e funcionalidade em uma extensão do Chrome, você precisa configurar o arquivo manifest.json. Crie um arquivo manifest.json na pasta public e cole o código abaixo:

{
    "manifest_version": 3,
    "name": "Kinsta Plugins Manager - Thanks to Kinsta API",
    "description": "This extension allows you to manage your WordPress site's plugin from Kinsta's MyKinsta dashboard via Kinsta API.",
    "version": "0.1.0",
    "icons": {
        "48": "kinsta-icon.png"
    },
    "action": {
        "default_popup": "index.html"
    },
    "permissions": [
        "tabs"
    ],
    "host_permissions": [
        "https://my.kinsta.com/*"
    ]
}

Certifique-se de adicionar o arquivo de ícone à sua pasta public.

Neste ponto, você pode executar o comando de build (npm run build) para que todos os seus ativos, inclusive o arquivo manifest.json, o index.html gerado pelo React e outros arquivos, sejam movidos para a pasta dist ou build.

Em seguida, navegue até chrome://extensions/ e carregue isso como uma extensão descompactada para o Chrome. Clique no botão Load Unpacked e selecione o diretório que você criou para sua extensão.

Restrinja a extensão a sites específicos

Você percebeu que essa extensão funciona a qualquer momento. Queremos que funcione somente quando um usuário estiver navegando no painel MyKinsta.

Para fazer isso, vamos ajustar o arquivo App.jsx. Crie um estado para armazenar a aba ativa:

const [activeTab, setActiveTab] = useState(null);

Em seguida, atualize o hook useEffect para definir e invocar a função getCurrentTab:

const getCurrentTab = async () => {
  const queryOptions = { active: true, currentWindow: true };
  const [tab] = await chrome.tabs.query(queryOptions);
  setActiveTab(tab);
}
getCurrentTab();

O código acima usa chrome.tabs.query com opções de consulta específicas para garantir que recupere somente a aba ativa na janela atual. Depois que a aba é recuperada, é definida como a aba ativa no estado da extensão.

Por fim, implemente uma lógica de renderização condicional na instrução de retorno do seu componente. Isso garante que a interface do usuário de gerenciamento de plugins seja exibida somente quando o usuário estiver no painel MyKinsta:

return (
  <div className="container">
    {activeTab?.url.includes('my.kinsta.com') ? (
      <div >
        <div className="title-section">
          <img src={KinstaLogo} className="logo" alt="" />
        </div>
        <p className="info-box">
          Get quick information about your site plugins that need update.
        </p>
        {isLoading ? (
          <p>Loading...</p>
        ) : (
          <>
            <div className="content">
              <p>The following {sitesWithPluginUpdate} sites have plugins that need to be updated.</p>
              {sitesWithOutdatedPlugin.map((site, index) => {
                return (
                  <PluginPage key={index} {...site} />
                );
              })}
            </div >
          </>
        )}
      </div >
    ) : (
      <div >
        <div className="title-section">
          <img src={KinstaLogo} className="logo" alt="" />
        </div>
        <p className="info-box">
          This extension is only available on Kinsta Dashboard.
        </p>
      </div>
    )}
  </div>
)

Após fazer as alterações, reconstrua seu aplicativo e recarregue a extensão do Chrome. Isso aplicará a nova lógica e as restrições.

Resumo

Neste artigo, você aprendeu os conceitos básicos da criação de uma extensão do Chrome e como criar uma com o React. Também aprendeu a criar uma extensão que interage com a API da Kinsta.

Como usuário da Kinsta, você pode aproveitar o enorme potencial e a flexibilidade que a API da Kinsta oferece, ajudando-o a desenvolver soluções personalizadas para gerenciar seus sites, aplicativos e bancos de dados.

Que endpoint da API da Kinsta você tem usado muito, e como? Compartilhe conosco na seção de comentários!

Joel Olawanle Kinsta

Joel is a Frontend developer working at Kinsta as a Technical Editor. He is a passionate teacher with love for open source and has written over 200 technical articles majorly around JavaScript and it's frameworks.