Endpoints fournis par une connexion
Définissez une source une seule fois et laissez chaque connexion fournir sa propre URL, sa base de données et son identifiant
Une source de données peut prendre l'endroit où elle se connecte dans la connexion qu'un administrateur crée, plutôt que dans son manifeste. Vous définissez « États Home Assistant » une seule fois ; un Home Assistant personnel et un Home Assistant d'entreprise sont deux connexions, chacune avec sa propre URL et son propre token. Rien de l'un ni de l'autre système n'est figé dans l'extension que vous publiez.
Deux éléments rendent cela possible, et ils vivent à des endroits différents :
| Quoi | Déclaré dans | Contient |
|---|---|---|
| Les champs de la connexion | L'extension — defineExtension({ configFields, auth }) | Une URL d'endpoint non secrète (ou un hôte, un port et une base de données), et l'identifiant |
| Les références de la source | La source de données — request.endpointRef, request.authRef, … | Les noms de ces champs. Jamais une valeur. |
Un administrateur renseigne les champs une fois, sur la connexion. La source les lit par leur nom à chaque exécution.
Déclarer la connexion sur l'extension
Les configFields sont les valeurs non secrètes que porte une connexion. Un champ que la plateforme va appeler se déclare avec type: 'url' et purpose: 'endpoint'. Les identifiants vont dans auth.credentials, exactement comme pour toute autre extension.
Une extension qui ne livre que des sources de données déclare les deux directement dans defineExtension — elle n'a besoin d'aucun toolkit :
import { defineExtension } from '@scrydon/sdk-authoring/extensions'
import { homeAssistantStates } from './data-source-states'
export default defineExtension({
id: 'home-assistant',
name: 'Home Assistant',
version: '1.0.0',
logo: './assets/icon.svg',
configFields: [
{
key: 'baseUrl',
label: 'Home Assistant URL',
type: 'url',
purpose: 'endpoint',
required: true,
placeholder: 'https://xxxx.ui.nabu.casa',
},
],
auth: {
credentials: { token: { type: 'apiKey', label: 'Long-lived access token' } },
default: 'token',
},
provides: { dataSources: [homeAssistantStates] },
})Un champ peut aussi porter des validations — { pattern, message, whenHostSuffix? } — que le formulaire de connexion et la plateforme vérifient avant d'utiliser la valeur (pour un champ url, le motif lit le chemin de l'URL). Les motifs s'exécutent sur la plateforme pour chaque valeur vérifiée ; extension build et l'installation refusent donc un motif qui peut provoquer un retour arrière catastrophique : répétition imbriquée, groupe à alternatives répété comme (a|aa)+, référence arrière, plus de trois répétitions de longueur variable, un nombre de répétitions supérieur à 25, ou un motif de plus de 512 caractères. Une valeur de plus de 256 caractères est refusée avant l'exécution de tout motif.
Lire l'endpoint dans la source
Là où une source écrirait url, elle écrit endpointRef — la clé du champ d'endpoint — et path, la partie qui suit l'URL de la connexion. authRef désigne l'identifiant :
import { defineDataSource } from '@scrydon/sdk-authoring/extensions'
export const homeAssistantStates = defineDataSource({
kind: 'table',
id: 'home-assistant-states',
vendor: 'home-assistant', // l'id de l'extension — ses connexions portent l'endpoint et le token
displayName: 'Home Assistant states',
scope: 'org',
table: {
name: 'ha_states',
primaryKey: ['entity_id'],
timestampColumn: 'last_changed',
columns: [
{ name: 'entity_id', dataType: 'string', isPrimaryKey: true },
{ name: 'state', dataType: 'string', nullable: true },
{ name: 'last_changed', dataType: 'timestamp' },
],
},
ingest: {
mode: 'poll',
intervalSec: 60,
request: {
endpointRef: 'baseUrl',
path: '/api/states',
authRef: 'token',
auth: { placement: 'bearer' },
},
response: { itemsPath: '$' },
mapping: {
entity_id: { path: 'entity_id' },
state: { path: 'state' },
last_changed: { path: 'last_changed' },
},
},
})La requête part vers l'URL de la connexion, suivie de path. Un chemin déjà présent dans l'URL est conservé ; un Home Assistant derrière un reverse proxy fonctionne donc tel quel :
| URL de la connexion | path | Requête |
|---|---|---|
https://xxxx.ui.nabu.casa | /api/states | https://xxxx.ui.nabu.casa/api/states |
https://proxy.example.com/ha | /api/states | https://proxy.example.com/ha/api/states |
Tout le reste de request — method, headers, query, auth, body — fonctionne exactement comme avec url. Les paramètres de requête statiques vont dans request.query, jamais dans path. Les sources on-demand utilisent le même bloc request.
Une requête désigne son endpoint exactement une fois : soit url, soit endpointRef et path. Définir les deux, ou aucun, est une erreur de build qui indique quelle clé retirer ou ajouter.
Un endpoint public, sans identifiant
Lorsque le service ne demande aucune authentification, la connexion ne porte que l'URL. Déclarez l'identifiant de l'extension comme none et omettez authRef dans la source :
// defineExtension({ ... })
configFields: [{ key: 'baseUrl', label: 'Feed URL', type: 'url', purpose: 'endpoint', required: true }],
auth: { credentials: { none: { type: 'none' } }, default: 'none' },
// defineDataSource({ ... }).ingest
request: { endpointRef: 'baseUrl', path: '/v1/readings' },L'administrateur crée tout de même une connexion — c'est là que réside l'URL — sans secret à saisir. Une source dont l'url est fixée dans le manifeste et qui ne demande aucun identifiant continue de fonctionner sans connexion, comme auparavant.
La règle du chemin
path est ajouté à une URL qu'un administrateur a choisie et que l'organisation a approuvée ; rien dans le chemin ne peut donc envoyer la requête ailleurs. Il doit :
- commencer par
/, et pas par//; - ne contenir que des caractères de chemin d'URL — ni espace, ni barre oblique inverse, ni caractère de contrôle ;
- ne contenir aucun segment
.ou..— pas non plus avec un paramètre;(/..;/admin), que Tomcat, Jetty et Spring résolvent comme..; - ne contenir ni
.,/ou\échappés (%2e,%2f,%5c), ni%échappé (%25, qu'un proxy qui décode deux fois lit comme un autre échappement), ni NUL (%00) ; - ne porter ni query string
?ni fragment#.
Chaque cas est une erreur de build qui nomme la règle. La même règle est appliquée à nouveau lorsque la requête est construite.
Streams
Un endpoint de stream accepte les deux mêmes formes. Avec endpointRef et path, la socket s'ouvre en wss:// sur l'hôte de la connexion :
ingest: {
mode: 'stream',
transport: 'websocket',
endpoint: { endpointRef: 'baseUrl', path: '/api/websocket', authRef: 'token' },
message: { itemsPath: '$' },
mapping: { /* ... */ },
}Le champ d'endpoint contient lui-même l'adresse https:// ; https://xxxx.ui.nabu.casa devient wss://xxxx.ui.nabu.casa/api/websocket.
Sources query
Une source query peut lire son hôte, son port et sa base de données depuis la connexion. Désignez les champs type: 'text' de la connexion dans endpoint.endpointRefs :
// defineExtension({ ... })
configFields: [
{ key: 'dbHost', label: 'Database host', type: 'text', required: true },
{ key: 'dbPort', label: 'Port', type: 'text', required: true, placeholder: '5432' },
{ key: 'dbName', label: 'Database', type: 'text', required: true },
],
auth: {
credentials: { warehouse: { type: 'basicAuth', label: 'Database login' } },
default: 'warehouse',
},
// defineDataSource({ ... })
credentialRef: 'warehouse',
ingest: {
mode: 'query',
engine: 'postgres',
endpoint: {
endpointRefs: { host: 'dbHost', port: 'dbPort', database: 'dbName' },
trustedCaRef: 'warehouse-ca',
},
tables: [{ schema: 'public', name: 'readings' }],
},- Chaque valeur est soumise à la même règle qu'une valeur écrite dans le manifeste : un nom d'hôte (sans schéma ni port), un port de 1 à 65535, un nom de base de données fait de lettres, de chiffres, de
_et de-. - L'ancre de confiance est désignée une seule fois — littéralement avec
endpoint.trustedCaRef, ou depuis un champ avecendpointRefs.trustedCaRef. TLS vérifie toujours l'identité du serveur ; il n'existe aucune option pour s'en dispenser. - L'identifiant est un identifiant
basicAuth(nom d'utilisateur et mot de passe). - Pour MySQL, le
schemad'une table est sa base de données : chaque table déclarée doit donc nommer la base de données fournie par la connexion. C'est vérifié au moment où les valeurs de la connexion sont lues.
Les valeurs de la connexion sont vérifiées à chaque enregistrement du catalogue, avant toute connexion. Rien ne se connecte tant qu'elles ne sont pas valides ; le catalogue reste pending et en indique la raison :
| Les valeurs de la connexion… | Le catalogue affiche | Correction |
|---|---|---|
| sont absentes (une valeur faite uniquement d'espaces compte comme absente) | pending · identifiant indisponible | Renseignez le champ sur la connexion. |
| désignent un hôte invalide, un port qui n'est pas composé de chiffres de 1 à 65535, ou une base de données invalide — ou une table MySQL désigne une autre base de données | pending · identifiant indisponible | Corrigez la valeur sur la connexion. |
| contiennent le mot de passe de la connexion | pending · identifiant indisponible | Retirez le mot de passe de la valeur. (Le nom d'utilisateur peut être égal au nom de la base de données — c'est courant et autorisé.) |
| désignent un hôte absent de la liste d'autorisation de sortie de l'organisation | pending · egress blocked | Un administrateur approuve l'hôte. |
| utilisent un port qu'aucun réseau de bases de données de votre déploiement n'ouvre | pending · egress blocked | Demandez à votre opérateur Scrydon d'ajouter le réseau de la base, avec ce port, aux réseaux privés du déploiement. |
Un catalogue enregistré avec les valeurs précédentes de la connexion cesse de les interroger dès qu'une vérification échoue.
Modifier l'hôte, le port ou la base de données réenregistre le catalogue auprès du nouveau serveur à la réconciliation suivante ; dès lors, les requêtes et les exécutions de copie ne vont plus qu'au nouveau serveur.
À chaque ouverture de la connexion — et tant qu'elle reste ouverte — chaque adresse vers laquelle l'hôte se résout est vérifiée à nouveau. Une base de données n'est joignable qu'à l'intérieur d'un réseau privé que votre opérateur Scrydon a ouvert pour votre organisation, sur un port que ce réseau ouvre ; un réseau situé dans une plage privée exige en plus que l'interrupteur Réseaux privés de votre organisation soit activé (Paramètres → Gouvernance → Egress). Les interrupteurs de l'organisation n'ouvrent jamais à eux seuls une adresse de base de données. L'ancre de confiance de la connexion doit être l'une de celles que ce réseau liste. Ces adresses sont toujours refusées, quelle que soit la configuration :
- le loopback (
127.0.0.0/8,::1) et les plages propres au cluster du déploiement ; - les adresses link-local et de métadonnées cloud (
169.254.0.0/16,fe80::/10,fd00:ec2::/64) et le WireServer d'Azure (168.63.129.16) ; - l'espace d'adressage partagé (CGNAT,
100.64.0.0/10), les plages de benchmark (198.18.0.0/15) et de documentation (192.0.2.0/24,198.51.100.0/24,203.0.113.0/24,2001:db8::/32),0.0.0.0/8, le multicast et l'espace réservé (224.0.0.0/4,240.0.0.0/4).
Un hôte de requête est un nom DNS. Une adresse IP est refusée comme hôte invalide — utilisez le nom que porte le certificat de la base de données.
Ce que fait l'administrateur
Un administrateur d'organisation ajoute le nom d'hôte de l'endpoint à la liste d'autorisation de sortie (egress allowlist) de l'organisation (Paramètres → Gouvernance → Egress). Seule une entrée explicite l'approuve : les domaines par défaut de la plateforme et les hôtes qu'une extension déclare pour ses outils ne suffisent pas. L'entrée doit comporter au moins deux libellés — example.com approuve ha.example.com, un com seul n'approuve rien. Une adresse IP n'est approuvée que par une entrée qui est exactement cette adresse, et seulement si elle se trouve dans l'un des réseaux privés du déploiement.
Dans Paramètres → Plateforme → Extensions → Home Assistant → Connexions, ajoutez une connexion. Un seul formulaire contient tout : un nom (« Home Assistant · personnel »), l'environnement, l'URL et le token.
La source lit cette connexion à sa prochaine exécution. Un second système est une seconde connexion, avec sa propre URL et son propre token.
L'URL est vérifiée avant toute connexion, et un refus indique quoi corriger :
| L'URL… | Résultat |
|---|---|
| est absente, ou faite uniquement d'espaces | La source signale qu'il lui faut une connexion. Rien n'est récupéré. |
n'est pas en https://, ou contient un nom d'utilisateur, un mot de passe, une query ? ou un fragment # | Refusée comme endpoint invalide. Rien n'est récupéré. |
contient le token ou le mot de passe de la connexion — y compris coupé par une tabulation ou un saut de ligne, ou encodé en pourcentage. Un token de 8 caractères ou plus est pris en compte où qu'il apparaisse ; un token plus court seulement s'il occupe toute une partie de l'adresse, comme un libellé du nom d'hôte ou un segment du chemin, si bien qu'un token de test d'une seule lettre n'entraîne pas le refus de https://example.org | Refusée. Saisissez le token dans le champ d'identifiant, pas dans l'adresse. |
| utilise un port autre que 443, ou atteint une adresse, qu'aucun réseau privé de votre déploiement n'ouvre | Refusée. Demandez à votre opérateur Scrydon d'ajouter le réseau aux réseaux privés du déploiement. |
| atteint un réseau privé que votre déploiement ouvre alors que l'interrupteur Réseaux privés de votre organisation est désactivé | Refusée. Activez Réseaux privés dans Paramètres → Gouvernance → Egress. |
| atteint une adresse réservée, ou l'une de celles de Scrydon | Refusée, toujours — les adresses listées ci-dessus pour les sources de requête. |
| désigne un hôte absent de la liste d'autorisation de sortie | Refusée tant qu'un administrateur n'a pas approuvé l'hôte. |
| désigne une adresse IP que la liste d'autorisation ne liste pas exactement | Refusée tant qu'un administrateur n'a pas ajouté cette adresse exacte. |
L'enregistrement ne refuse que ce qui ne va pas dans la valeur elle-même ; qu'un réseau ou la liste d'autorisation l'admette peut changer plus tard, donc la connexion est enregistrée et la disponibilité et l'état de synchronisation de la source indiquent ce qui manque. Le port et chaque adresse sont vérifiés à nouveau à chaque connexion de la source — un nom qui se résout ensuite ailleurs est refusé à ce moment-là.
Le même message indique à l'administrateur quoi corriger, qu'il apparaisse sous le champ du formulaire de connexion ou dans l'état de synchronisation de la source. La disponibilité ne juge les champs de la connexion que pour un propriétaire ou un administrateur de l'organisation : un membre de l'espace de travail voit si la connexion, le compte et l'identifiant de la source sont en place, et découvre ce qu'il en est de l'adresse de la connexion dans l'état de synchronisation de la source, dès que celle-ci s'exécute.
Une connexion est choisie pour l'environnement dans lequel la source s'exécute ; une source de Production ne se rabat jamais sur une connexion de Développement.
Les lignes de journal et l'état de synchronisation n'affichent jamais l'URL ni l'hôte de la connexion. Une source qui lit son endpoint depuis une connexion est désignée par le champ et le chemin — « the connection's Home Assistant URL at /api/states » — et l'hôte, ou toute adresse vers laquelle il se résout, est remplacé par le libellé du champ partout où une erreur le répète. Une source dont l'url est fixe est affichée avec son URL, tout identifiant qu'elle contient masqué. Dans les deux cas, une source qui détient un identifiant n'affiche rien de ce qu'ont dit le serveur ou le réseau : une requête en échec n'indique que sa ligne de statut (HTTP 401 from …) ou « request failed for … », car un serveur peut renvoyer l'identifiant sous une forme qu'aucun masque ne reconnaît.
Les adresses locales passent par votre opérateur. Un Home Assistant standard à http://homeassistant.local:8123 est en HTTP clair, qui n'est jamais utilisé. Servez-le en HTTPS, puis soit donnez-lui un nom d'hôte public (Home Assistant Cloud, ou un reverse proxy sur le port 443), soit demandez à votre opérateur Scrydon d'ajouter son réseau et son port aux réseaux privés du déploiement pour votre organisation — avec votre autorité de certification si elle signe le certificat — et activez Réseaux privés.
Ce que vérifie le build
extension build, extension validate et le contrôle d'installation de la plateforme refusent une extension dont les sources de données désignent quelque chose qu'elle ne déclare pas. Chaque message nomme la source et l'élément manquant :
| Refusé | Correction |
|---|---|
endpointRef (ou une entrée de endpointRefs) ne désigne aucun champ de configuration | Ajoutez le champ à configFields — type: 'url', purpose: 'endpoint' pour une requête ou un stream, type: 'text' pour une partie d'endpoint query |
endpointRef désigne un champ qui n'est pas un champ d'endpoint | Déclarez-le avec type: 'url' et purpose: 'endpoint' |
authRef ou credentialRef ne désigne aucun identifiant | Ajoutez-le à auth.credentials |
L'identifiant d'une source query n'est pas basicAuth | Déclarez-le avec type: 'basicAuth' |
Le vendor de la source n'est pas l'id de l'extension | Donnez à vendor l'id de l'extension — celle dont les connexions portent l'endpoint et l'identifiant |
L'extension.id du manifeste de code diffère de celui d'extension.json | Une extension a un seul id ; donnez la même valeur aux deux |
Pourquoi un champ nommé plutôt qu'un modèle d'URL. Un placeholder comme {{connection.baseUrl}} pourrait apparaître n'importe où dans une URL, y compris dans l'hôte : « l'hôte ne vient que d'un endpoint approuvé par un administrateur » ne serait alors qu'un espoir. endpointRef plus path en fait la forme même du manifeste : l'hôte ne peut venir de nulle part ailleurs.