Microeden Agry SDK (ITA)
1. Che cos’è Agry Dev Mode
Agry Dev Mode è l’ambiente di sviluppo e test per le estensioni eseguite dentro Agry. Il plugin viene caricato dall’applicazione host, ma il codice di terze parti viene eseguito in un runtime isolato. Il confine di isolamento fa parte del modello di sicurezza: il plugin riceve metodi espliciti del bridge invece di accedere direttamente alla pagina, allo storage del browser, al DOM dell’host o alla sessione autenticata.
Dev Mode è disponibile solo per un account sviluppatore autenticato:
- è necessaria una sessione Microeden persistente;
- il token sviluppatore deve corrispondere a quello fornito a Dev Mode;
- l’account deve avere l’abilitazione agli strumenti di sviluppo.
Essere utenti Pro non è sufficiente per ottenere l’accesso a Dev Mode o la pubblicazione nello Store.
2. Modello di runtime e ciclo di vita
Un plugin isolato viene normalmente eseguito come modulo JavaScript asincrono o come funzione asincrona auto-eseguita:
(async function () {
Agry.log("Plugin avviato", "info");
// Registra qui interfaccia ed eventi.
})();
La facciata principale è esposta come Agry. Una seconda facciata, MyMicroeden, espone i servizi dell’account e dei dispositivi. Entrambe sono proxy che comunicano tramite un bridge RPC validato.
Ciclo di vita consigliato:
- Registra widget, pulsanti della ribbon, finestre e listener.
- Leggi solo i dati necessari alla vista corrente.
- Usa lo storage isolato del plugin per lo stato che deve sopravvivere a un ricaricamento.
- Registra
Agry.onStopper la pulizia. - Rimuovi timer, handler, marker e finestre quando il plugin viene fermato.
Agry.onStop(function () {
Agry.time.clearInterval(refreshTimer);
Agry.events.remove("activity.created", onActivity);
});
I callback vengono registrati tramite il bridge e devono rimanere piccoli e serializzabili. Non conservare oggetti DOM dell’host e non tentare di chiamare le API globali del browser dal plugin.
3. Esempio minimo di plugin
(async function () {
var fields = await Agry.fields.list();
await Agry.addWidget(
"weather-widget",
"Meteo del campo",
"☀️",
"<p id=\"weather-widget-content\">Campi disponibili: " + fields.length + "</p>"
);
// I widget non restituiscono un handle setHTML. Aggiorna il contenuto tramite ID.
await Agry.dom.setHTML(
"weather-widget-content",
"<p>Campi disponibili: " + fields.length + "</p>"
);
Agry.onMapClick(function (point) {
Agry.log("Click sulla mappa: " + point.lat + ", " + point.lng, "info");
});
})();
Usa i metodi del DOM isolato per l’interfaccia del plugin. Restituiscono handle limitati al plugin; non possono selezionare nodi arbitrari dell’applicazione.
4. Riferimento API Agry
I metodi seguenti sono disponibili nel runtime isolato di terze parti. Restituiscono promise salvo quando la descrizione indica la registrazione di un callback.
Il bridge attuale usa argomenti posizionali per le API di mappa, widget, finestre e ribbon. Non accetta gli oggetti di opzioni usati da alcuni prototipi precedenti. I setter DOM ricevono l’ID di un elemento appartenente al plugin, non un nodo DOM del browser.
4.1 Informazioni runtime e log
Agry.version— stringa della versione API del runtime. La sandbox attualmente comunica la versione3.3 (sandbox).Agry.log(message, type)— scrive un messaggio diagnostico. I tipi comuni sonoinfo,success,warnederror.Agry.toast(message, type)— mostra un toast breve nell’host.Agry.onStop(callback)— registra una funzione di pulizia eseguita quando il plugin viene fermato.
4.2 Campi
Agry.fields.list()— restituisce i campi/aree di progetto visibili all’account corrente.Agry.fields.getDiary(fieldName)— restituisce le voci del diario di un campo.Agry.fields.getTasks(fieldName)— restituisce le attività programmate e completate di un campo.Agry.fields.getFieldIcon(fieldName)— restituisce l’icona o emoji del campo usata da Agry.Agry.getFields()— helper di compatibilità che restituisce i campi.Agry.getNearestField(latitude, longitude)— individua il campo visibile più vicino quando la posizione è disponibile.
I record restituiti sono snapshot di dati. Considera opzionali nomi, identificativi, date, attività, elementi, meteo e assegnazioni ai collaboratori, perché i progetti meno recenti possono non avere le proprietà introdotte successivamente.
4.3 Diario e attività
Diario
Agry.diary.addEntry(fieldName, noteText, photoBase64) crea una voce del diario. La foto è opzionale e deve essere una data URL base64 solo quando è stata effettivamente allegata.
await Agry.diary.addEntry(
"Campo nord",
"Crescita regolare dopo l’irrigazione.",
null
);
Attività programmate
Agry.tasks.schedule(fieldName, activityType, dateISO, time)crea un’attività programmata.timepuò esserenullper un’attività che dura tutto il giorno.Agry.tasks.reschedule(fieldName, activityType, originalDateISO, newDateISO)sposta un’attività a una nuova data.- Non esiste un metodo pubblico isolato per elencare le attività: leggile con
Agry.fields.getTasks(fieldName).
Usa date ISO (YYYY-MM-DD) e, se presente, un orario a 24 ore (HH:mm). Non dedurre il fuso orario concatenando stringhe: usa gli helper per le date o i valori restituiti dall’host.
Se un’attività è assegnata a un collaboratore, conserva l’identificativo e il nome visualizzato restituiti dall’host. Non usare l’indirizzo email come etichetta visuale quando è disponibile il nome.
4.4 Magazzino
Agry.warehouse.getInventory()— restituisce l’inventario disponibile per il progetto/account corrente.Agry.warehouse.addItem(item)— aggiunge o aggiorna un articolo.Agry.warehouse.removeItem(itemId)— rimuove un articolo.Agry.warehouse.save()— rende persistenti le modifiche in attesa.
I dati del magazzino sono protetti da capability. Se l’accesso viene negato, mostra uno stato vuoto esplicito invece di presumere che il magazzino sia vuoto.
4.5 Accesso alla rete
Agry.fetch(url, options)— esegue una richiesta validata verso un servizio HTTPS esterno.Agry.httpGet(url)— helper per una richiesta GET.Agry.httpPost(url, body)— helper per una richiesta POST JSON.
Le richieste passano attraverso il firewall della sandbox. Vedi le sezioni dedicate a rete e sandbox. Il plugin non può usare fetch, XHR, WebSocket, EventSource o gli import nativi del browser.
var response = await Agry.fetch("https://api.example.com/status", {
method: "GET",
headers: { "Accept": "application/json" }
});
var status = await response.json();
Non inviare credenziali, cookie di sessione, token CSR o altri segreti. Un dominio può richiedere il consenso esplicito quando la richiesta esporta dati sensibili del progetto.
4.6 Mappa
Agry.map.setView(latitude, longitude, zoom)— cambia la vista della mappa.Agry.map.drawShape(type, coordinates, options)— disegna una forma di proprietà del plugin.Agry.map.clearLayers()— rimuove i layer del plugin.Agry.map.getUserPosition()— richiede la posizione corrente quando autorizzata.Agry.addMarker(latitude, longitude, popupContent, iconSource, alertLevel)— aggiunge un marker del plugin.Agry.removeMarker(markerHandle)— rimuove un marker restituito daAgry.addMarker.Agry.clearAllMarkers()— rimuove tutti i marker del plugin.Agry.onMapClick(callback)— registra un callback per i click sulla mappa.
Esempio di marker:
var marker = await Agry.addMarker(
45.123,
11.456,
"<strong>Osservazione arnia</strong>",
"🐝",
"normal"
);
Marker e layer appartengono al plugin. Rimuovili durante la pulizia se non devono rimanere dopo la chiusura.
await Agry.map.drawShape(
"circle",
[45.123, 11.456],
{ radius: 100, color: "#89ba54", fillOpacity: 0.2 }
);
4.7 Pulsanti ribbon, widget e finestre
Agry.addRibbonButton(label, icon, onClickFunc)— aggiunge un pulsante alla ribbon Agry.Agry.addWidget(widgetId, title, icon, contentHTML)— crea un widget dashboard. Una chiamata ripetuta con lo stesso ID non crea duplicati e restituisce un valore di successo, non un handle del widget.Agry.createWindow(windowId, title, contentHTML, width, height)— apre una finestra o un pannello del plugin.Agry.onWindowClose(windowId, callback)— registra un callback alla chiusura della finestra.Agry.drawRiskLink(startCoord, endCoord, level)— disegna un collegamento di rischio/relazione supportato dal livello visuale Agry.
Usa identificativi stabili, titoli brevi, emoji o icone approvate e contenuti appartenenti al plugin. Widget e finestre non restituiscono handle modificabili: aggiorna il contenuto tramite il bridge DOM isolato.
await Agry.createWindow(
"my-plugin-panel",
"Rapporto del campo",
"<p id=\"report-content\">Rapporto pronto.</p>",
560,
"auto"
);
await Agry.dom.setHTML("report-content", "<p>Rapporto aggiornato.</p>");
4.8 Finestre di dialogo
Agry.ui.confirm(message, callback)Agry.ui.prompt(message, defaultValue, callback)
Usano i dialog dell’host. Il risultato viene fornito al callback e non è garantito un valore awaitable.
Agry.ui.confirm("Eliminare questo record del plugin?", function (confirmed) {
if (confirmed) {
Agry.log("Operazione confermata", "warning");
}
});
Non fare affidamento su window.confirm, window.prompt o alert del browser: queste globali non sono disponibili nel runtime isolato.
4.9 DOM e stili
Il bridge DOM isolato fornisce:
Agry.dom.getElementById(id)Agry.dom.createElement(tagName)Agry.dom.querySelector(selector)Agry.dom.removeElement(id)Agry.dom.getValue(id)Agry.dom.setValue(id, value)Agry.dom.setHTML(id, html)Agry.dom.setStyles(id, styles)Agry.dom.setAttributes(id, attributes)Agry.styles.set(styleId, cssText)
I primi tre metodi restituiscono handle limitati al plugin; gli handle espongono gli helper di compatibilità css, attr, html e val. I setter e il remover DOM espliciti ricevono la stringa dell’ID. Mantieni selettori e ID nello spazio dei nomi del plugin. L’HTML inserito deve essere sottoposto a escaping quando contiene dati dell’utente o del progetto.
4.10 Eventi
Agry.events.add(eventName, callback)Agry.events.remove(eventName, callback)Agry.events.getTargetId()
I nomi degli eventi sono definiti dall’host e possono estendersi nel tempo. Il callback dovrebbe ricevere un payload semplice e serializzabile e restituire rapidamente il controllo.
4.11 Storage e file
Lo storage del plugin è isolato dal local storage del sito:
Agry.storage.set(key, value)Agry.storage.get(key)Agry.storage.clear()— cancella le chiavi del plugin corrente.Agry.storage.getAllKeys()
Per i download dell’utente:
Agry.file.download(filename, data, mimeType)— avvia un download nell’interfaccia host.
Non usare localStorage, sessionStorage, IndexedDB, Cache Storage o accesso diretto al filesystem: sono intenzionalmente non disponibili.
4.12 Date, orari e utility
Helper per date:
Agry.date.now()Agry.date.getHours(date)Agry.date.getMinutes(date)Agry.date.getSeconds(date)Agry.date.format(date, withTime)Agry.date.addDays(date, days)Agry.date.diffDays(firstDate, secondDate)Agry.date.toISO(date)
Timer:
Agry.time.setTimeout(callback, delay)Agry.time.setInterval(callback, delay)Agry.time.clearTimeout(id)Agry.time.clearInterval(id)
Utility:
Agry.utils.getDistance(a, b)— calcolo della distanza;Agry.utils.formatArea(area)— formattazione di un’area.
L’host completo contiene altri helper, ma la facciata isolata espone intenzionalmente solo il sottoinsieme sicuro indicato sopra.
4.13 Snapshot del database
Agry.DB contiene snapshot in sola lettura usati dalle funzioni UI supportate:
Agry.DB.areaIconsAgry.DB.categoriesAgry.DB.wildlifeAgry.DB.cropAgry.DB.alertsAgry.DB.presetsAgry.DB.schemas
Lo snapshot può essere assente o incompleto nei progetti meno recenti. Non modificarlo e non considerarlo un’API di scrittura.
5. Bridge SDK MyMicroeden
Il runtime isolato espone una facciata MyMicroeden limitata:
var profile = await MyMicroeden.account.getProfile();
var devices = await MyMicroeden.devices.list();
Account
MyMicroeden.account.getProfile()— informazioni come nome e ruoli, nel rispetto del consenso.MyMicroeden.account.getPreferences()— preferenze isolate del plugin.MyMicroeden.account.setPreference(key, value)— salva una preferenza del plugin.MyMicroeden.account.deletePreference(key)— elimina una preferenza del plugin.
Dispositivi
MyMicroeden.devices.list()— dispositivi visibili all’account corrente.MyMicroeden.devices.getHistory(deviceId, options)— telemetria/storico limitati.MyMicroeden.devices.getCategories()— categorie dei dispositivi.
Non dare per scontati altri namespace dello SDK del sito. Il plugin non deve chiamare direttamente endpoint interni per aggirare bridge o controlli di capability.
6. Capability e consenso
Le API sensibili sono protette dal gestore delle capability dei plugin. Gli scope di base attuali includono:
| Capability | Scopo |
|---|---|
| account-profile-read | Leggere nome e ruoli dell’account |
| preferences-read | Leggere preferenze isolate del plugin |
| preferences-write | Scrivere preferenze isolate del plugin |
| devices-read | Leggere dispositivi e telemetria limitata |
| project-data-read | Leggere campi, attività e geometrie |
| diary-write | Creare voci del diario |
| tasks-write | Creare o modificare attività |
| warehouse-read | Leggere i dati del magazzino |
| warehouse-write | Modificare i dati del magazzino |
| location-read | Leggere la posizione dell’utente |
| plugin-storage-write | Usare lo storage isolato del plugin |
| file-download | Avviare un download |
| sensitive-data-export:hostname | Esportare dati sensibili verso uno specifico host esterno |
Il consenso viene conservato per plugin e account finché l’utente non lo revoca. Dev Mode può usare una scelta temporanea durante i test, ma un plugin pubblicato deve funzionare correttamente anche quando il consenso viene negato o revocato.
Principi per le capability:
- Richiedi il set minimo necessario alla funzione.
- Spiega nell’interfaccia o nei metadati perché una capability è necessaria.
- Gestisci una capability negata come uno stato normale.
- Non inserire segreti nel codice, nei metadati o nello storage.
- Non usare una richiesta di rete per evitare una richiesta di consenso.
7. Politica di rete
Ogni richiesta esterna viene validata prima di uscire dal runtime.
- È obbligatorio HTTPS.
- La lunghezza dell’URL è limitata a 2048 caratteri.
- Gli URL con nome utente o password vengono rifiutati.
- Gli URL same-origin e
microeden.ioo i suoi sottodomini vengono rifiutati dal bridge di rete del plugin. - Sono bloccati localhost, nomi locali/interni/LAN/home/corp, indirizzi privati, link-local, riservati, multicast e literal IPv6.
- Sono bloccati i domini nella blacklist del firewall.
- I metodi consentiti sono GET, HEAD, POST, PUT, PATCH e DELETE.
- GET e HEAD non possono avere un body.
- I body devono essere stringhe e sono limitati a 32 KiB.
- Sono accettati al massimo 20 header, ognuno limitato a 2048 caratteri.
- Sono bloccati gli header cookie, host, origin, referer, proxy authorization, CSR-token e CSRF-token.
- Il budget predefinito è di 20 richieste per esecuzione.
- Ogni richiesta ha un timeout di otto secondi.
- Le risposte sono limitate a 5 MiB.
- Credenziali omesse, redirect rifiutati, cache disabilitata e referrer non inviato.
Usa un’integrazione server-side per credenziali e rate limiting. Non inserire mai una chiave API in un plugin distribuito nello Store.
8. Limiti della sandbox (informativi)
Questa sezione è una guida per gli sviluppatori terzi e non garantisce che i limiti non cambino. L’implementazione usa attualmente un iframe isolato e un bridge worker/RPC. Le globali native del browser e gli oggetti diretti dell’host non sono intenzionalmente disponibili.
I limiti di validazione attuali includono:
- massimo 30 argomenti RPC;
- massimo 20 chiamate RPC simultanee;
- stringa massima per argomento: 16.384 caratteri;
- profondità massima degli oggetti annidati: 7;
- lunghezza massima degli array: 500;
- massimo 120 chiavi per oggetto;
- le chiavi devono rispettare
[A-Za-z0-9_.-]{1,96}; - le chiavi
__proto__,prototypeeconstructorvengono rifiutate; - oggetti ciclici e non plain vengono rifiutati;
- massimo 96 callback registrati;
- budget della richiesta serializzata: 32 KiB;
- budget della risposta isolata ordinaria: 5 MiB;
- budget della risposta SDK MyMicroeden: 256 KiB.
Il runtime blocca inoltre i tentativi di riferirsi o invocare:
window,document,globalThis,self,navigator,locationehistory;localStorage,sessionStorage,indexedDBecaches;XMLHttpRequest,WebSocket,EventSource,importScripts,Worker,SharedWorker,BroadcastChanneleMessageChannel;WebAssembly,eval,Function, import dinamici efetchnativo;- trucchi basati su
constructor,prototypeepostMessage.
Questi controlli sono difesa in profondità. Il plugin deve comunque validare gli input, fare escaping del testo visualizzato e proteggere ogni backend di sua proprietà.
9. Requisiti per sorgente e pacchetto
Un plugin dello Store è identificato da un ID stabile e dai suoi metadati. Una sorgente di sviluppo tipica contiene un entry point JavaScript e un manifest nella directory del plugin. Lo schema esatto del pacchetto viene validato dal catalogo corrente; i soli metadati locali non sono sufficienti per la pubblicazione.
Metadati consigliati:
- ID stabile;
- nome leggibile;
- versione semantica;
- categoria;
- descrizione sintetica;
- icona o emoji;
- fino a tre screenshot;
- sorgente di ingresso;
- capability richieste;
- informativa sulla privacy e sui servizi esterni.
Mantieni la sorgente sotto il limite della Console sviluppatori e dividi i contenuti statici molto grandi in dati o risorse gestite dall’host quando possibile.
10. API della Console sviluppatori
Le operazioni della Console sviluppatori sono riservate al proprietario autenticato della console e non costituiscono un’API runtime pubblica. Le richieste richiedono:
- una sessione autenticata persistente;
- l’abilitazione agli strumenti di sviluppo;
- un header
MICROEDEN-CSR-TOKENvalido; - i controlli same-site/referer applicati dal servizio.
Azioni supportate:
| Azione | Descrizione |
|---|---|
| list | Elenca i plugin dello sviluppatore e i relativi stati |
| quota | Legge la quota di sorgente/storage |
| open_source | Legge la sorgente corrente |
| autosave_source | Salva una bozza automatica |
| save_draft | Salva una bozza normale |
| submit_review | Invia un plugin alla revisione |
| update_metadata | Aggiorna nome, versione, categoria, descrizione, icona e screenshot |
| delete | Elimina un plugin dello sviluppatore |
Limiti correnti dei metadati:
- nome: obbligatorio, massimo 120 caratteri;
- versione: obbligatoria, massimo 30 caratteri;
- categoria: obbligatoria, massimo 80 caratteri;
- descrizione: obbligatoria, massimo 2.000 caratteri;
- icona: massimo 512 caratteri;
- screenshot: massimo 3 URL, ognuno di massimo 1.500 caratteri;
- codice sorgente: massimo 45.000 caratteri.
Gli stati di revisione comuni sono draft, pending, review, testing, approved, published e rejected. I record in attesa, revisione o test sono bloccati per la modifica ordinaria. La modifica di un plugin approvato, pubblicato o rifiutato crea una nuova revisione in bozza.
Il server esegue una scansione di sicurezza statica. Segnala l’accesso alle globali del browser, i costruttori di rete diretti, l’esecuzione dinamica del codice, gli import dinamici, fetch nativo, i trucchi basati su prototype/constructor e il messaging tra finestre. Un avviso della scansione non sostituisce la revisione manuale.
La sorgente del plugin viene cifrata lato server con AES-256-GCM (enc:v2) e può essere compressa prima del salvataggio. La cifratura protegge la sorgente memorizzata, ma non rende sicura una logica non sicura durante l’esecuzione.
11. Caricamento dello Store e modalità di esecuzione
Il catalogo corrente dello Store viene caricato dal catalogo cloud autenticato. I metadati del catalogo e la sorgente del plugin sono operazioni separate. Se una voce non possiede una sorgente di migrazione, l’host comunica un errore di migrazione/sorgente; l’autore deve verificare che la sorgente cloud sia pubblicata e che l’ID stabile non sia cambiato.
I plugin di terze parti vengono eseguiti in modalità isolata. Solo una piccola integrazione legacy esplicitamente autorizzata usa la modalità host precedente. Un plugin non deve mai dipendere da accessi legacy, globali interne o selettori DOM esterni al proprio spazio.
12. Modelli per plugin affidabili
Lettura e rendering
Leggi il minimo indispensabile, normalizza i campi opzionali, fai escaping del testo utente e mostra stati distinti per caricamento, vuoto, errore e successo.
function escapeText(value) {
return String(value == null ? "" : value)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/\"/g, """)
.replace(/'/g, "'");
}
Aggiornamento
Usa Agry.time.setInterval solo quando la funzione richiede il polling, scegli un intervallo prudente e cancellalo in Agry.onStop. Evita di aggiornare mentre l’utente sta modificando un modulo.
Errori
Cattura gli errori del bridge e della rete, registra i dettagli tecnici con Agry.log e mostra un toast breve e utile con Agry.toast. Non visualizzare risposte server grezze che potrebbero contenere segreti.
Idempotenza
Usa ID stabili per widget, finestre, marker e record salvati. Prima di creare un duplicato, leggi lo stato corrente del plugin; chiamate ripetute a widget o finestre con lo stesso ID vengono gestite dall’host senza creare un insieme illimitato di oggetti.
Date
Conserva le date in formato ISO e visualizzale con gli helper dell’host. Rispetta il fuso orario rappresentato dai dati delle attività restituiti.
Collaboratori e progetti
Usa il nome visualizzato fornito da Agry per le assegnazioni ai collaboratori. Rispetta le regole di visibilità dell’host: un collaboratore vede solo i progetti e le attività condivisi con lui, mentre il proprietario può vedere l’intero progetto.
13. Risoluzione dei problemi
Argomento sandbox troppo lungo
Riduci la dimensione del singolo argomento RPC. Non passare un intero progetto, un’immagine o un file sorgente a un solo metodo. Memorizza o invia i dati in blocchi limitati, usa un URL/riferimento quando appropriato e mantieni i body sotto i limiti indicati.
widget.setHTML is not a function
Agry.addWidget(...) restituisce un valore di successo, non un oggetto widget modificabile. Passa l’HTML iniziale come quarto argomento e aggiorna un elemento del plugin con Agry.dom.setHTML(elementId, html) oppure usa il metodo html di un handle DOM.
Coordinate marker non valide
Agry.addMarker usa argomenti posizionali. Chiamarlo con un oggetto di opzioni fa diventare latitudine e longitudine NaN, che il bridge rifiuta correttamente. Usa Agry.addMarker(latitude, longitude, popupContent, iconSource, alertLevel) e coordinate finite nei rispettivi intervalli validi.
ID finestra non consentito
Anche Agry.createWindow usa argomenti posizionali. Il primo argomento deve essere un ID stabile che rispetta [A-Za-z][A-Za-z0-9_-]{0,63} e non deve essere un ID riservato all’host. Passare un oggetto produce la stringa [object Object], che il bridge sicuro rifiuta. Usa Agry.createWindow("my-plugin-panel", title, contentHTML, width, height).
Capability negata
L’utente non ha concesso lo scope richiesto oppure lo ha revocato. Mostra uno stato vuoto esplicativo e continua con le capability ancora disponibili.
Sorgente del plugin non disponibile
Verifica ID stabile, voce del catalogo cloud, sorgente pubblicata e stato della revisione. I metadati locali legacy non forniscono automaticamente la sorgente dello Store.
Comportamento UI inatteso
Controlla l’uso diretto delle globali browser, i selettori non isolati, la registrazione duplicata degli eventi e i timer non cancellati. Usa ID stabili e il bridge DOM isolato.
Richiesta di rete bloccata
Controlla HTTPS, autorizzazione dell’host, dimensioni, metodo, header, body e budget delle richieste. Non tentare di aggirare il firewall con XHR, WebSocket, data URL o un secondo proxy.
14. Checklist di compatibilità
Prima di inviare un plugin:
- Leggi
Agry.versione usa il feature detection per i metodi opzionali. - Prova il plugin senza campi, attività, articoli di magazzino o dispositivi.
- Prova capability negate e sessioni scadute.
- Prova testi lunghi, nomi mancanti, record vecchi e date non valide.
- Prova un servizio esterno lento o non raggiungibile.
- Ferma e riavvia il plugin per verificare la pulizia.
- Conferma che credenziali e token non siano inclusi nella sorgente o nei log.
- Esegui la scansione di sicurezza della Console sviluppatori e risolvi ogni avviso.
- Verifica metadati, screenshot, informativa privacy e capability richieste.
- Mantieni il comportamento del plugin dentro la facciata isolata documentata qui.
15. Nota sulla gestione delle versioni
Questo riferimento descrive l’implementazione disponibile al momento della sua generazione. L’host può aggiungere API o rendere più restrittivi i limiti senza modificare i dati dei plugin esistenti. Considera privati i metodi non documentati, evita di dipendere da endpoint interni e fornisci sempre un fallback quando un metodo o una capability non è disponibile.