nostr-rest-api-field-mapping-gap

Par divinevideo · divine-mobile

Corriger les fonctionnalités cassées par les réponses de l'API REST qui aplatissent les événements Nostr, entraînant la perte des données de tag. À utiliser quand : (1) Une fonctionnalité fonctionne pour les événements chargés via WebSocket mais pas via l'API REST, (2) Un champ de modèle (comme sha256, textTrackRef) est null pour les données de l'API REST mais renseigné pour les données WebSocket, (3) L'API REST retourne des champs dénormalisés (d_tag, video_url) mais omet le tableau brut des tags, (4) hasSubtitles/hasFeature retourne false pour les vidéos chargées via REST. Courant dans les clients Nostr qui utilisent à la fois des API REST (pour l'analytique/les requêtes en masse) et WebSocket (pour les événements en temps réel).

npx skills add https://github.com/divinevideo/divine-mobile --skill nostr-rest-api-field-mapping-gap

Lacune de correspondance de champs Nostr REST API

Problème

Les clients Nostr qui utilisent à la fois des API REST et des connexions WebSocket pour charger les événements peuvent avoir des fonctionnalités qui fonctionnent de façon incohérente. Les fonctionnalités qui dépendent des valeurs de balises Nostr (comme la balise x pour sha256, ou les balises personnalisées pour les pistes de texte) fonctionnent quand les événements sont chargés via WebSocket (qui fournit l'événement brut complet avec toutes les balises), mais se cassent quand les mêmes événements sont chargés via l'API REST (qui renvoie du JSON aplati/dénormalisé sans le tableau de balises brutes).

Contexte / Conditions de déclenchement

  • Une fonctionnalité fonctionne pour certaines vidéos/événements mais pas d'autres (source de données dépendante)
  • Un élément UI (comme un bouton CC) s'affiche pour les événements chargés par WebSocket mais pas pour ceux de l'API REST
  • Un modèle a des champs nuls qui devraient être remplis (sha256, textTrackRef, etc.)
  • La réponse de l'API REST a des champs comme d_tag, video_url, title mais PAS de tableau tags
  • La factory fromJson() analyse les balises pour des valeurs comme x (sha256), mais l'API REST n'inclut pas les balises brutes
  • hasSubtitles, hasFeature, ou des getters similaires retournent false pour les données de l'API REST

Cause racine

Les API REST qui indexent les événements Nostr font généralement :

  1. Extraire les valeurs de balises couramment utilisées dans des colonnes dédiées (d_tag, title, thumbnail, etc.)
  2. NE PAS retourner le tableau complet de balises brutes dans leur réponse
  3. Peuvent avoir des champs sémantiquement équivalents sous des noms différents

Par exemple, dans un système vidéo basé sur Blossom :

  • La balise Nostr x contient le hash de contenu SHA256
  • L'API REST retourne d_tag (qui pour les uploads Blossom EST le SHA256) mais pas sha256
  • La méthode fromJson() du modèle essaie de trouver sha256 à partir du champ direct ou de la balise x, ne trouve rien
  • Les fonctionnalités qui dépendent de sha256 (comme la récupération de sous-titres) se cassent silencieusement

Solution

Étape 1 : Identifier la lacune de correspondance

Comparer ce que l'API REST retourne vs ce que le modèle a besoin :

# Récupérer un exemple de réponse de l'API REST
curl -s "https://your-api.example.com/api/videos?limit=1" | jq '.[0] | keys'

Vérifier quels champs du modèle sont nuls après analyse :

  • Regarder la méthode factory fromJson()
  • Identifier les champs remplis à partir de l'analyse de balises (tableau tags) qui n'existeront pas dans la réponse REST
  • Vérifier si des champs de l'API REST sont sémantiquement équivalents mais portent des noms différents

Étape 2 : Ajouter des correspondances de secours

Dans la méthode fromJson() du modèle, ajouter la logique de secours APRÈS l'analyse primaire :

// Primaire : essayer le champ direct et les balises
var sha256 = eventData['sha256']?.toString() ?? json['sha256']?.toString();

// Analyser à partir des balises si disponible
if (eventData['tags'] is List) {
  // ... analyse de balises pour la balise 'x' ...
}

// Normaliser
if (sha256 != null && sha256.isEmpty) sha256 = null;

// SECOURS : Utiliser le champ sémantiquement équivalent de l'API REST
// Pour les uploads Blossom, d_tag EST le hash de contenu (64 caractères hex)
if (sha256 == null && dTag.length == 64 && _isHex(dTag)) {
  sha256 = dTag;
}

Étape 3 : Valider que le secours est sûr

S'assurer que le secours ne se déclenche que lorsqu'approprié :

  • Vérifier le format/la longueur (SHA256 = 64 caractères hex)
  • Ne pas écraser les valeurs explicitement définies
  • Gérer les cas limites (importations classiques où d_tag N'EST PAS un hash)

Étape 4 : Ajouter des tests

test('falls back to d_tag as sha256 when d_tag is 64-char hex', () {
  final json = {
    'd_tag': 'a04b70820ef370e90aae19d23e46b1482d3af0e7c9d994d1594a1384a62d3972',
    // No sha256 field, no tags array (REST API response)
    ...otherFields,
  };
  final model = Model.fromJson(json);
  expect(model.sha256, equals(json['d_tag']));
});

test('does not use d_tag as sha256 when d_tag is not a hex hash', () {
  final json = {'d_tag': 'my-video-slug', ...otherFields};
  final model = Model.fromJson(json);
  expect(model.sha256, isNull);
});

test('does not override explicit sha256 with d_tag', () {
  final json = {
    'd_tag': 'a04b708...', // 64 hex
    'sha256': 'explicit-value',
    ...otherFields,
  };
  final model = Model.fromJson(json);
  expect(model.sha256, equals('explicit-value'));
});

Vérification

  1. Charger l'application et naviguer vers un flux qui utilise l'API REST (par ex., tendances/découverte)
  2. Vérifier que la fonctionnalité fonctionne (par ex., le bouton CC s'affiche ET les sous-titres s'affichent quand activés)
  3. Vérifier aussi que les événements chargés via WebSocket fonctionnent toujours (par ex., le flux accueil des utilisateurs suivis)
  4. Vérifier que les d_tags non-hash (importations classiques, slugs personnalisés) ne provoquent pas de faux positifs

Approche de débogage

Quand une fonctionnalité fonctionne de façon incohérente entre les événements :

  1. Identifier la source de données : L'événement cassé provient-il de l'API REST ou du WebSocket ?
  2. Comparer les données brutes : Récupérer le même événement des deux sources, diff les champs
  3. Suivre le pipeline : Réponse API -> fromJson() -> modèle -> getter -> condition UI
  4. Vérifier les gardes conditionnels : Regarder if (model.hasX) ou if (field != null) dans l'UI
  5. Enregistrer au niveau du modèle : Ajouter un enregistrement temporaire dans fromJson() pour voir ce qui est nul

Notes

  • Ce pattern s'applique à TOUT client Nostr qui utilise à la fois des sources de données REST et WebSocket
  • L'API REST peut évoluer pour inclure plus de champs au fil du temps, donc l'approche de secours est compatible avec l'avenir (les champs explicites ont la priorité sur les secours)
  • Envisager de demander à l'équipe de l'API REST d'ajouter directement les champs manquants
  • La validation _isHex() prévient les faux positifs des valeurs d_tag non-hash
  • Des lacunes similaires peuvent exister pour d'autres champs dérivés de balises (textTrackRef, duration, etc.)

Skills associés

  • rest-api-optimistic-update-race-condition - Un autre problème de cohérence des données de l'API REST
  • nostr-addressable-event-d-tag-requirement - Gestion d_tag associée

Skills similaires