Come utenti di Google Chrome, probabilmente avrete utilizzato alcune estensioni del browser. Vi siete mai chiesti come sono costruite o come crearne una?

Questo articolo vi guida attraverso il processo di creazione di un’estensione per Chrome, in particolare una che utilizza React e l’API di Kinsta per gestire i plugin sui siti WordPress ospitati con Kinsta.

Cos’è un’estensione di Chrome?

Un’estensione di Chrome è un programma che viene installato nel browser Chrome e ne migliora le funzionalità. Le estensioni possono variare da semplici pulsanti a icona nella barra degli strumenti a funzioni completamente integrate che interagiscono profondamente con la propria esperienza di navigazione.

Come creare un’estensione di Chrome

Creare un’estensione di Chrome è come sviluppare un’applicazione web, ma richiede un file in formato JSON chiamato manifest.json. Questo file costituisce la struttura portante dell’estensione, dettando le impostazioni, i permessi e le funzionalità che si desidera includere.

Per iniziare, creiamo una cartella che conterrà tutti i file dell’estensione. Successivamente, creiamo un file manifest.json nella cartella.

Un file manifest.json di base per un’estensione di Chrome include proprietà chiave che definiscono le impostazioni di base dell’estensione. Di seguito è riportato un esempio di file manifest.json che include i campi necessari per il suo funzionamento:

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

Possiamo caricarlo e testarlo come estensione scompattata in Chrome. Andiamo su chrome://extensions nel browser e attiviamo la modalità sviluppatore, quindi clicchiamo sul pulsante Load Unpacked. Si aprirà un browser di file e potremo selezionare la directory creata per la nostra estensione.

Caricare un'estensione di Chrome facendo clic su Load unpacked in modalità sviluppatore
Caricare un’estensione di Chrome cliccando su Load unpacked in modalità sviluppatore.

Cliccando sull’icona dell’estensione, non succederà nulla perché non abbiamo ancora creato un’interfaccia utente.

Creare un’interfaccia utente (popup) per l’estensione Chrome

Come per ogni applicazione web, l’interfaccia utente (UI) dell’estensione utilizza l’HTML per strutturare il contenuto, il CSS per lo stile e il JavaScript per aggiungere interattività.

Creiamo un’interfaccia utente di base utilizzando tutti questi file. Iniziamo creando un file HTML (popup.html). Questo file definisce la struttura degli elementi dell’interfaccia utente, come testo, titoli, immagini e pulsanti. Aggiungiamo il seguente codice:

<!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>

Il codice qui sopra crea un titolo, un paragrafo e un pulsante. Anche i file CSS e JavaScript sono collegati. Ora aggiungiamo alcuni stili nel file popup.css:

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

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

Poi, nel file popup.js, aggiungiamo un ascoltatore di eventi al pulsante in modo che quando viene cliccato, venga visualizzato un avviso:

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!'),
    });
});

Questo codice JavaScript recupera la scheda attiva corrente e utilizza la Chrome Scripting API per eseguire uno script che visualizza un avviso con un messaggio di saluto quando viene cliccato il pulsante Say Hello. In questo modo si introduce un’interattività di base nell’estensione di Chrome.

Grazie a questi passaggi, abbiamo creato una semplice interfaccia utente popup per la nostra estensione Chrome che include testo, stile e funzionalità di base.

Infine, dobbiamo abilitare il file popup nel file manifest.json aggiungendo alcuni permessi:

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

Nella configurazione qui sopra, la chiave default_popup specifica che popup.html sarà l’interfaccia utente predefinita quando l’utente interagisce con l’estensione. L’array permissions include i permessi scripting e tabs, che sono fondamentali affinché l’estensione possa interagire con le schede e utilizzare le funzioni di scripting del browser.

L’array host_permissions specifica con quali siti l’estensione può interagire. I modelli http://*/* e https://*/* indicano che l’estensione può interagire con tutti i siti web accessibili tramite i protocolli HTTP e HTTPS.

Con queste impostazioni nel file manifest.json, l’estensione Chrome è correttamente configurata per visualizzare un popup ed eseguire script.

Aggiornare l’estensione di Chrome

Dopo aver apportato queste modifiche nella cartella locale, dobbiamo aggiornare la cartella scompattata caricata su Chrome. Per farlo, apriamo la pagina delle estensioni di Chrome, individuiamo l’estensione e clicchiamo sull’icona refresh.

Fare clic sull'icona di refresh per aggiornare l'estensione.
L’icona di refresh per aggiornare l’estensione.

A questo punto, possiamo cliccare sull’icona dell’estensione e apparirà un popup. Cliccando sul pulsante Say Hello, apparirà un avviso.

Ora abbiamo una conoscenza di base su come iniziare a creare un’estensione per Chrome. Ma possiamo fare molto di più. Possiamo manipolare l’interfaccia utente del sito, fare richieste API, recuperare dati da URL per eseguire operazioni specifiche e altro ancora.

Come creare un’estensione per Chrome con React

Come abbiamo detto in precedenza, la creazione di un’estensione di Chrome è simile alla creazione di un’applicazione web. Possiamo utilizzare framework web popolari come React.

Per React, il file manifest.json viene creato nella cartella public. Questa cartella viene utilizzata per le risorse statiche che non si desidera vengano elaborate da Webpack (o da altri bundler simili che React potrebbe utilizzare in strumenti come Create React App).

Quando costruiamo la nostra applicazione React, il processo di build copia tutti i contenuti della cartella public nella cartella dist. Ecco come creare un’estensione di Chrome con React:

  1. Creiamo una nuova applicazione React. Possiamo utilizzare l’ambiente di sviluppo locale Vite eseguendo questo comando nel terminale:
npm create vite@latest

Quindi, diamo un nome al progetto e selezioniamo React come framework. Una volta fatto questo, navighiamo nella cartella del progetto e installiamo le dipendenze:

cd <project-name>
npm install
  1. Nella cartella public del progetto React, creiamo un file manifest.json. Aggiungiamo le seguenti configurazioni:
{
    "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://*/*"
    ]
}

La configurazione di un’estensione Chrome include un oggetto action che imposta index.html come popup predefinito quando si clicca sull’icona dell’estensione. Questo è il file HTML statico generato quando costruiamo un’applicazione React.

  1. È il momento di sviluppare l’applicazione React. Qui possiamo sctenare la nostra fantasia e fare richieste API, stilizzarle come vogliamo, usare React Hooks e altro ancora.
  1. Una volta finito di costruire l’interfaccia utente dell’estensione, eseguiamo il comando di build in React (npm run build). Tutte le risorse, compresi il file manifest.json, l’index.html generato da React e altri, vengono spostate nella cartella dist o build.
  2. Infine, carichiamo l’estensione in Chrome. Andiamo su chrome://extensions/ ed eseguiamo il refresh dell’estensione.

Creare un’estensione Chrome per gestire i plugin di un sito con l’API di Kinsta

Ecco come apparirà l’estensione Chrome che creeremo:

Estensione Chrome costruita con React che interagisce con l'API di Kinsta
Estensione Chrome costruita con React che interagisce con l’API di Kinsta.

Facendo clic, l’estensione mostra un elenco di siti con plugin obsoleti sul nostro account MyKinsta. Possiamo vedere l’elenco dei plugin e cliccare sul pulsante Visualizza in MyKinsta per accedere alla pagina Temi e plugin del sito, dove potremo aggiornare ogni plugin.

Vediamo come creare l’estensione per Chrome.

L’API di Kinsta

L’API di Kinsta è un potente strumento che permette di interagire in modo programmatico con i servizi di Kinsta come i siti WordPress ospitati. Può aiutare ad automatizzare diverse attività legate alla gestione di WordPress, tra cui la creazione di un sito, il recupero di informazioni sul sito, lo stato del sito, la consultazione e il ripristino dei backup e altro ancora.

Per utilizzare l’API di Kinsta, è necessario avere un account con almeno un sito WordPress, un’applicazione o un database in MyKinsta. Inoltre, dovremo generare una chiave API per autenticarci e accedere al nostro account.

Per generare una chiave API:

  1. Andiamo alla dashboard di MyKinsta.
  2. Andiamo alla pagina delle chiavi API (Nome > Impostazioni dell’azienda > Chiavi API).
  3. Clicchiamo su Crea chiave API.
  4. Scegliamo una scadenza o impostiamo una data di inizio personalizzata e un numero di ore di scadenza della chiave.
  5. Assegniamo alla chiave un nome univoco.
  6. Clicchiamo su Genera.

Dopo aver creato una chiave API, copiamola e conserviamola in un luogo sicuro (utilizzando ad esempio un gestore di password). Possiamo generare più chiavi API, che saranno elencate nella pagina delle chiavi API. Per revocare una chiave API, basta cliccare sul pulsante Revoca.

Gestire i plugin del sito con Kinsta API e React

Iniziamo a sviluppare un’interfaccia utente in React, che verrà poi trasformata in un’estensione per Chrome. Questa guida presuppone una familiarità di base con React e l’interazione con le API.

Impostazione dell’ambiente

Innanzitutto, nel file App.jsx, definiamo una costante per l’URL dell’API di Kinsta per evitare ridondanze nel codice:

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

Per sicurezza, memorizziamo i dati sensibili come la chiave API e l’ID dell’azienda Kinsta in un file .env.local per tenerli al sicuro e fuori dal codice sorgente:

VITE_KINSTA_COMPANY_ID=YOUR_COMPANY_ID
VITE_KINSTA_API_KEY=YOUR_API_KEY

Recuperare i dati con l’API di Kinsta

Nel file App.jsx, dobbiamo effettuare diverse richieste all’API di Kinsta per recuperare informazioni sui siti e sui loro plugin.

  1. Recuperare i siti dell’azienda: iniziamo recuperando l’elenco dei siti associati al nostro account azienda su Kinsta. Utilizziamo l’ID dell’azienda in una richiesta GET, che restituisce un array di dettagli del sito.
    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. Recuperare i dati dell’ambiente per ogni sito: per ogni sito, recuperiamo gli ambienti, che includono l’ID dell’ambiente necessario per ulteriori richieste. Questo comporta la mappatura di ogni sito e una chiamata API all’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. Recuperare i plugin per ogni ambiente del sito: infine, utilizziamo l’ID dell’ambiente per recuperare i plugin per ogni sito. Questo passaggio prevede una funzione di mappatura e una chiamata API all’endpoint /sites/environments/${environmentId}/plugins per ogni 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,
          };
        });

    Ora possiamo riunire tutte queste richieste in una funzione che viene utilizzata per restituire l’array finale di siti con i dettagli di base di ogni sito e dei suoi plugin:

    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;
    }

Visualizzare i dati dei siti

Creiamo uno stato con l’hook useState per memorizzare i siti con i plugin obsoleti. L’hook useEffect chiamerà anche il metodo getSitesWithPluginData() per estrarre i dettagli del sito quando il componente viene montato.

Nell’hook useEffect, creiamo una funzione che esegua un ciclo su ogni sito per filtrare i siti con plugin obsoleti e poi memorizzarli nello stato:

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);
}, []);

Nel codice qui sopra, si nota che anche lo stato di caricamento è stato creato e impostato su true per impostazione predefinita. Questo verrà utilizzato per controllare la visualizzazione dei dati. Quando tutti i dati sono stati caricati, lo impostiamo su false.

Di seguito è riportato un markup per rendere i dati del sito e i plugin all’interno dell’interfaccia utente.

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

Il codice include un’intestazione con un logo e un paragrafo informativo. Il contenuto dell’interfaccia utente viene renderizzato in modo condizionale in base allo stato di isLoading. Se i dati sono ancora in fase di caricamento, viene visualizzato un messaggio di caricamento. Una volta che i dati sono stati caricati, vengono presentati i dati relativi ai siti e agli eventuali plugin che necessitano di aggiornamenti.

Notiamo anche un componente PluginPage (PluginPage.jsx). Questo componente è stato progettato per visualizzare i singoli siti e i dettagli dei loro plugin. Include una funzionalità per attivare la visibilità dei dettagli dei 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

Configurare il file manifest

Per trasformare l’interfaccia utente e le funzionalità in un’estensione di Chrome, dobbiamo configurare il file manifest.json.

Creiamo un file manifest.json nella cartella public e incolliamo il codice qui sotto:

{
    "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/*"
    ]
}

Assicuriamoci di aggiungere il file dell’icona alla cartella public.

A questo punto, possiamo eseguire il comando di build (npm run build) in modo che tutte le risorse, compresi il file manifest.json, l’index.html generato da React e altri file, vengano spostati nella cartella dist o build.

Successivamente, andiamo su chrome://extensions/ e carichiamo l’estensione in Chrome come estensione scompattata. Clicchiamo sul pulsante Load Unpacked e selezioniamo la cartella creata per l’estensione.

Limitare l’estensione a siti specifici

Questa estensione funziona in qualsiasi momento. Noi, però, vogliamo che funzioni solo quando un utente naviga verso la dashboard di MyKinsta.

Per farlo, modifichiamo il file App.jsx. Creiamo uno stato per memorizzare la scheda attiva:

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

Quindi, aggiorniamo l’hook useEffect per definire e invocare la funzione getCurrentTab:

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

Il codice precedente utilizza chrome.tabs.query con opzioni di interrogazione specifiche per assicurarsi di recuperare solo la scheda attiva nella finestra corrente. Una volta recuperata la scheda, questa viene impostata come scheda attiva nello stato dell’estensione.

Infine, implementiamo una logica di rendering condizionale nella dichiarazione di ritorno del componente. Questo assicura che l’interfaccia di gestione del plugin venga visualizzata solo quando l’utente si trova nella dashboard di 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>
)

Dopo aver apportato le modifiche, eseguiamo nuovamente la build dell’applicazione e riaggiorniamo l’estensione Chrome. In questo modo verranno applicate le nuove logiche e restrizioni.

Riepilogo

In questo articolo abbiamo appreso le basi della creazione di un’estensione per Chrome e come crearne una con React. Abbiamo anche imparato a creare un’estensione che interagisce con l’API di Kinsta.

Come utenti di Kinsta, potete sfruttare l’enorme potenziale e la flessibilità dell’API di Kinsta per sviluppare soluzioni personalizzate per la gestione di siti, applicazioni e database.

Quale endpoint dell’API di Kinsta state usando più spesso e come lo utilizzate? Condividetelo con noi nella sezione commenti!

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.