27 Journal
Charles-Édouard Coste edited this page 2025-07-27 18:16:37 +02:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

2025-07-27

La version 2.0 de FluffyChat va peut-être changer la donne (et ma motivation). Si je dis que mon serveur est en version 1.80 (qui nexiste pas) alors les logs JS indiquent :

[Matrix] Server supports the versions: Instance of 'minified:wN' but this application is only compatible with {v1.1, v1.2, v1.3, v1.4, v1.5, v1.6, v1.7, v1.8, v1.9, v1.10, v1.11, v1.12, v1.13, v1.14}.

La dernière version de Matrix est la 1.15 donc cest pas mal… Pendant ce temps, Element dit toujours nimporte quoi : "Votre serveur est trop ancien…"

Comment faire confiance à un client qui dit que la version 273829849842.0.0 est trop vielle alors quil accepte la version 0.0.00001 ?

En tout cas Element est Fluffy sont donc désormais supposés être tous les deux compatibles avec la version 1.9.

Jai donc enfin une version commune entre deux clients "grand public".

2024-09-27

Jai ajouté suffisamment de routes pour quElement commence à appeler /sync. Javais déjà atteint ce stade lors de ma dernière tentative d'implémentation en mode "quick and dirty", mais à l'époque, je n'avais pas réussi à résoudre certains problèmes, notamment celui de gérer efficacement les appels à /sync pendant la création des nouveaux "batchs".

Explication : Lorsqu'un client se connecte, il appelle /sync une ou plusieurs fois avec différents paramètres pour récupérer les événements passés (regroupés en "batchs"). Une fois cette étape terminée, les appels suivants à /sync servent à récupérer les nouveaux événements en temps réel. Tant qu'aucun événement ne survient, la réponse de la requête est vide, et celle-ci se termine rapidement, ce qui entraîne un enchaînement d'appels successifs dElement, chaque nouvelle requête étant déclenchée dès que la précédente est terminée.

La dernière fois que jai expérimenté ce comportement sur mon hébergement mutualisé OVH, jai reçu des alertes mindiquant que les performances de mon hébergement allaient être réduites pour éviter de consommer les ressources partagées avec d'autres clients (ce qui est compréhensible).

Une solution envisageable est de mettre en place du "long polling". Autrement dit, au lieu de répondre immédiatement quil ny a rien à signaler, le serveur maintient la connexion ouverte et attend 1, 2, 5, voire 10 secondes (ou plus) avant de répondre. Ainsi, cela permet de retarder la requête suivante si aucun événement ne se produit pendant le délai dattente, ou bien de renvoyer une réponse lorsqu'un événement survient. Cette approche est théoriquement plus efficace que de répondre immédiatement, mais elle implique de maintenir des connexions ouvertes en permanence, ce qui n'est pas le point fort de PHP. En effet, PHP n'est pas conçu pour gérer des connexions persistantes. Des solutions comme Mercure ont été envisagées pour contourner ce problème. Sur un hébergement professionnel on pourrait régler le problème avec du roadrunner par exemple, mais sur un hébergement mutualisé cette approche pourrait rapidement atteindre la limite des connexions parallèles autorisées rendant le reste des ressources indisponibles, tandis que la méthode précédente laissait au moins la possibilité à d'autres clients de se glisser dans la file dattente.

Aujourdhui, jai testé une autre idée : appliquer un rate limit pour demander au client de patienter avant de réessayer. Cependant, les spécifications de Matrix indiquent que la route /sync ne doit pas être soumise à une "rate-limit". Après quelques essais, jai pu confirmer quElement ignore complètement lindication retry_after_ms lorsque je renvoie une erreur 429 lors dun appel à /sync. En réalité, Element marque une pause après la requête, mais cela semble uniquement lié au fait quune erreur est survenue. Une erreur 404 produit d'ailleurs le même effet. Je ne pense donc pas que ce soit une solution viable, à moins que les spécifications de Matrix ne changent davis et autorisent une rate-limit sur /sync. Mais cela prendra probablement du temps avant de voir des changements dans ce sens.

Cependant, une question me vient à lesprit : comment cela fonctionne-t-il lorsquun service de notifications est utilisé ? Est-ce quElement cesse de flooder /sync et attend une notification avant de reprendre ses requêtes ? Si cest le cas, cela pourrait être une piste intéressante à explorer.

2024-09-24

Je me rends assez compte que quand on bosse tout seul sur un projet dont tout le monde se fout, cest compliqué de faire tout correctement au niveau de la gestion de projet.

Dans un premier temps, il vaudrait mieux que je fasse un genre de prototype pourri et impossible à maintenir simplement pour que les gens puissent se faire une idée de ce que cela pourrait être mais aussi pour moi-même afin de me rendre compte plus vite des difficultés à venir. Surtout quil y a le protocole dun côté et la réalité des implémentations des clients de lautre.

Je pense donc faire une branche "xp" (pour eXtrem Programming) qui repartirait du premier commit de la branche master mais avec le code le plus simpliste possible et sans tests automatisés. Une fois que jaurai un truc à peu prêt fonctionnel, il servira dinspiration pour reprendre le reste du projet proprement.

2024-09-17

Cela fait un bon moment que je nai pas avancé sur ce projet, et c'est en partie lié à un problème que javais noté le 12 décembre 2023 : Element adoptait un comportement étrange concernant la prise en charge des versions de Matrix par le serveur. Par exemple, lorsque jessayais dimplémenter la dernière version disponible, la 1.9, il me signalait qu'elle était trop ancienne et ne fonctionnait correctement que lorsque jannonçais la compatibilité avec la version 1.1, qui est pourtant bien plus ancienne. Rien de plus démotivant que de devoir implémenter un protocole avec sept versions mineures de retard, tout en sachant quil faudra un jour combler cet écart.

Récemment, j'ai repris mes recherches sur le sujet et découvert ce ticket qui traite du problème, ainsi que celui-ci qui demande une clarification de la documentation. Ce dernier a été ouvert quelques jours après mes derniers efforts et na reçu une réponse quen avril 2024. Cependant, il semble indiquer qu'il n'est plus nécessaire d'implémenter une version aussi ancienne de Matrix pour les clients utilisant la bibliothèque matrix-js-sdk.

En effet, à ce jour, Element continue de se plaindre lorsque je limite la compatibilité aux versions 1.9, 1.10 ou 1.11, mais ne dit plus rien pour la version 1.8.

Par contre, fluffy chat est toujours bloqué en 1.6, visiblement.

2023-12-16

Concernant lenvironnement de développement, phpactor ça rame un peu, voir beaucoup. Lauto-completion, si ça doit faire perdre 2 secondes à chaque fois quon veut écrire un truc, cest vraiment naze. De plus, sous NixOS, la configuration est un peu complexe car il veut savoir où chercher le binaire de psalm, php-cs-fixer, ou autre alors que ce dernier change à chaque nouvelle version. La solution que jai trouvée est de rajouter ces outils à la liste de packages de lutilisateur. Ainsi léquivalent de /usr/local/bin devient /etc/profiles/per-user/<user>/bin. Mais il y a un autre soucis : certains scripts sont empaquetés dans dautres. En réalité le psalm-language-server, par exemple, est dans /etc/profiles/per-user/<user>/share/php/psalm/bin (on le voit en faisant cat /etc/profiles/per-user/<user>/bin/psalm-language-server.

Je pense quil reste plus intéressant de coder, puis de demander à psalm son avis après. Plutôt que de devoir prendre une machine de guerre pour coder.

Toujours concernant lenvironnement de développement, le proxy de symfony-cli me permet de développer en local avec un nom de domaine arbitraire et avec https. Ça devrait maider à aller développer beaucoup plus vite. Il ne me manque plus quà mettre un Element et un FluffyChat en local aussi plutôt que de les charger depuis le web. Je me demande même si je ne peux pas les ajouter sur certaines routes pour les embarquer dans le serveur. À voir si cela peut créer des problèmes de licences.

2023-12-13

Jai un peu avancé hier sur lauthentification. Ai trouvé un article intéressant sur les #MapRequestPayload qui montre aussi comment faire des objets DTO.

Par contre, il faut que je réfléchisse à comment optimiser mon environnement de développement. Déployer systématiquement en ligne, ce nest pas pratique. Il faut que je puisse faire des boucles essai/erreur en local.

2023-12-12

Je me suis occupé hier de la "découverte de service" mais quelque chose détrange se passe quand jutilise Element, qui est quand même un peu la référence : quand la route _matrix/client/versions indique que je ne prend en charge que la version 1.9 (qui est la plus récente à ce jour) il me dit :

Votre serveur daccueil est trop
ancien et ne prend pas en
charge la version minimale
requise de lAPI.

Cest un peu fort de café.

Je narrive pas non plus à savoir quelle version, element attend. Ça commence à devenir pénible. Au moins, côté fluffy chat, lapplication indique les choses clairement :

Le serveur daccueil prend en charge les versions des spécifications : "v1.9"
Mais cette application ne prend en charge que "v1.1, v1.2"

Par contre, merci flutter et les webcompotent hypes à la mors-moi le nœud ! Parce que jai dû recopier cette phrase à la main. Ça semble à la mode davoir des trucs qui ne permettent plus de faire des copier/coller.

Malgré tout, je me demande si je ne vais pas viser la version 1.2 du protocole pour le coup.

Après réflexion, en effet, je vais viser la version 1.2 du protocole. Peut-être quen avançant sur limplémentation, Element se mettra à accepter mon serveur. En attendant, jaurai au moins fluffy chat qui pourra fonctionner ou me donner des logs pertinents.

2023-12-11

Jai hâte de commencer à coder mais, malheureusement, je tiens à mettre en place toutes les bonnes pratiques que jattends dun projet. Entre autre, le système dintégration continue, avec une analyse de la qualité de code avant de commencer à produire.

Jai déjà récupéré un fichier shell.nix dun autre projet :

{ pkgs ? import <nixpkgs> {}}:

let

php = pkgs.php.buildEnv {
  extraConfig = "memory_limit=-1";
};

in

pkgs.mkShell {
  name = "dev";

  packages = [
    php
    php.packages.composer
    php.packages.php-cs-fixer
    php.packages.psalm
    pkgs.symfony-cli
    pkgs.sqlitebrowser
  ];
}

et un fichier default.nix :

{ pkgs ? import <nixpkgs> {}}:

let

php = pkgs.php.buildEnv {
  extraConfig = "memory_limit=-1";
};

in

  pkgs.stdenv.mkDerivation {
    name = "project";
    src = ./.;

    buildPhase = ''
      SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
      ${php.packages.composer}/bin/composer install --prefer-dist --optimize-autoloader
      ${php}/bin/php bin/console cache:clear --no-warmup
    '';

    installPhase = ''
      cp -r . $out/
    '';
  }

Pour construire le projet dans un dossier en lecture seule, il me suffira de faire nix-build --option sandbox false (ne fonctionne que si mon utilisateur NixOS est dans la liste des "trusted-users" ou si je suis sur un nix installé sur une autre distribution GNU/Linux) et de lancer un serveur PHP embedded :

export APP_LOG_DIR=/tmp/symfo/logs
export APP_CACHE_DIR=/tmp/symfo/cache
php -S 127.0.0.1:8000 -t result/public

La version du nixpkgs nest pas épinglée pour le moment, mais probablement que je la fixerai quand je saurai un peu mieux quelle version de PHP je vais viser.

Comme il ny a encore aucun code, la commande psalm --init a généré un fichier visant le niveau le plus haut de qualité. Cela va me permettre davancer un peu en mode "test driven development" : si la qualité baisse, je la corrige tout de suite (ou je revois mes exigences mais je noterai quelque part mes raisons pour le faire).

À noter que Psalm pense que les contrôleurs ne sont pas utilisés si on ne précise pas @psalm-api en annotation. Il existe peut-être un réglage à mettre dans psalm.xml pour régler cela.

2023-12-10

Voyons voir déjà comment se comportent des clients de base et un serveur classique tel que matrix.org au niveau des toutes premières interactions…

Avec Cinny, lorsque lon lance lapplication web, on a trois requêtes qui partent daffilée.

  1. Un GET https://matrix.org/.well-known/matrix/client dont la réponse est la suivante :
{
    "m.homeserver": {
        "base_url": "https://matrix-client.matrix.org"
    },
    "m.identity_server": {
        "base_url": "https://vector.im"
    },
    "org.matrix.msc3575.proxy": {
        "url": "https://slidingsync.lab.matrix.org"
    }
}
  1. Un POST https://matrix-client.matrix.org/_matrix/client/r0/register dont le payload et la réponse (un 401 Unauthorized) sont respectivement :
{"refresh_token":true}
{
    "session": "FMNyukjvdaTOLyqDLnxbzgnL",
    "flows": [
        {
            "stages": [
                "m.login.recaptcha",
                "m.login.terms",
                "m.login.email.identity"
            ]
        }
    ],
    "params": {
        "m.login.recaptcha": {
            "public_key": "6LcgI54UAAAAABGdGmruw6DdOocFpYVdjYBRe4zb"
        },
        "m.login.terms": {
            "policies": {
                "privacy_policy": {
                    "version": "1.0",
                    "en": {
                        "name": "Terms and Conditions",
                        "url": "https://matrix-client.matrix.org/_matrix/consent?v=1.0"
                    }
                }
            }
        }
    }
}
  1. Un GET https://matrix-client.matrix.org/_matrix/client/r0/login dont la réponse est
{
    "flows": [
        {
            "type": "m.login.sso",
            "identity_providers": [
                {
                    "id": "oidc-github",
                    "name": "GitHub",
                    "icon": "mxc://matrix.org/sVesTtrFDTpXRbYfpahuJsKP",
                    "brand": "github"
                },
                {
                    "id": "oidc-google",
                    "name": "Google",
                    "icon": "mxc://matrix.org/ZlnaaZNPxtUuQemvgQzlOlkz",
                    "brand": "google"
                },
                {
                    "id": "oidc-gitlab",
                    "name": "GitLab",
                    "icon": "mxc://matrix.org/MCVOEmFgVieKFshPxmnejWOq",
                    "brand": "gitlab"
                },
                {
                    "id": "oidc-facebook",
                    "name": "Facebook",
                    "icon": "mxc://matrix.org/nsyeLIgzxazZmJadflMAsAWG",
                    "brand": "facebook"
                },
                {
                    "id": "oidc-apple",
                    "name": "Apple",
                    "icon": "mxc://matrix.org/QQKNSOdLiMHtJhzeAObmkFiU",
                    "brand": "apple"
                }
            ]
        },
        {
            "type": "m.login.token"
        },
        {
            "type": "m.login.password"
        },
        {
            "type": "m.login.application_service"
        }
    ]
}

Element, a un comportement sensiblement différent. Après la requête GET /.well-known/matrix/client, il fait :

  • un GET https://matrix-client.matrix.org/_matrix/client/versions
  • un GET https://vector.im/_matrix/identity/v2
  • un GET https://matrix-client.matrix.org/_matrix/client/v3/thirdparty/protocols
  • un autre GET https://matrix-client.matrix.org/_matrix/client/versions avec len-tête "Accept: application/json" cette fois-ci au lieu de "Accept: /" la première fois
  • un GET https://matrix-client.matrix.org/_matrix/client/v3/voip/turn
  • et un POST https://matrix-client.matrix.org/_matrix/client/v3/keys/upload.

Cette différence est en partie dûe au fait que app.element.io fait que la requête sur /_matrix/client/versions lui permet de savoir que le serveur supporte certaines routes qui nappartiennent quà des versions plus récentes du protocole.

Fluffychat fait aussi une requête pour connaître les versions implémentées par le serveur, mais se contente ensuite de deux requêtes login :

  • GET https://matrix-client.matrix.org/_matrix/client/v3/login
  • GET https://matrix-client.matrix.org/_matrix/client/r0/login

Pourquoi tester à la fois sur v3 et r0 ? Aucune idée… Dautant plus que les deux retournent un 200 OK comme status.

Pour information… Matrix.org répond ça à GET https://matrix-client.matrix.org/_matrix/client/versions

{
    "versions": [
        "r0.0.1",
        "r0.1.0",
        "r0.2.0",
        "r0.3.0",
        "r0.4.0",
        "r0.5.0",
        "r0.6.0",
        "r0.6.1",
        "v1.1",
        "v1.2",
        "v1.3",
        "v1.4",
        "v1.5",
        "v1.6",
        "v1.7",
        "v1.8",
        "v1.9"
    ],
    "unstable_features": {
        "org.matrix.label_based_filtering": true,
        "org.matrix.e2e_cross_signing": true,
        "org.matrix.msc2432": true,
        "uk.half-shot.msc2666.query_mutual_rooms": true,
        "io.element.e2ee_forced.public": false,
        "io.element.e2ee_forced.private": false,
        "io.element.e2ee_forced.trusted_private": false,
        "org.matrix.msc3026.busy_presence": false,
        "org.matrix.msc2285.stable": true,
        "org.matrix.msc3827.stable": true,
        "org.matrix.msc3440.stable": true,
        "org.matrix.msc3771": true,
        "org.matrix.msc3773": false,
        "fi.mau.msc2815": false,
        "fi.mau.msc2659.stable": true,
        "org.matrix.msc3882": false,
        "org.matrix.msc3881": false,
        "org.matrix.msc3874": false,
        "org.matrix.msc3886": false,
        "org.matrix.msc3912": false,
        "org.matrix.msc3981": false,
        "org.matrix.msc3391": false,
        "org.matrix.msc4069": false
    }
}

2023-12-09

Je crée le dépôt. Jai fait le choix décrire ce journal en français car je veux pouvoir écrire de façon fluide, sans devoir réfléchir dans une autre langue que la mienne. Le code, les commentaires, et la doc seront en anglais.

Je pars dune version LTS de Symfony. Symfony car je fais le choix arbitraire dutiliser ce framework (tout comme celui de faire ce projet en PHP) et LTS (6.4) parce que je ne pense pas avoir le temps pour suivre les mises à niveaux tous les 6 mois. Du moins dans un premier temps.