Table of Contents
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 n’existe 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 c’est pas mal… Pendant ce temps, Element dit toujours n’importe quoi : "Votre serveur est trop ancien…"
Comment faire confiance à un client qui dit que la version 273829849842.0.0 est trop vielle alors qu’il 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.
J’ai donc enfin une version commune entre deux clients "grand public".
2024-09-27
J’ai ajouté suffisamment de routes pour qu’Element commence à appeler /sync. J’avais 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 d’Element, chaque nouvelle requête étant déclenchée dès que la précédente est terminée.
La dernière fois que j’ai expérimenté ce comportement sur mon hébergement mutualisé OVH, j’ai reçu des alertes m’indiquant 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 qu’il n’y 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 d’attente, 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 d’attente.
Aujourd’hui, j’ai 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, j’ai pu confirmer qu’Element ignore complètement l’indication retry_after_ms lorsque je renvoie une erreur 429 lors d’un appel à /sync. En réalité, Element marque une pause après la requête, mais cela semble uniquement lié au fait qu’une 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 d’avis 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 à l’esprit : comment cela fonctionne-t-il lorsqu’un service de notifications est utilisé ? Est-ce qu’Element cesse de flooder /sync et attend une notification avant de reprendre ses requêtes ? Si c’est 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, c’est 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 qu’il y a le protocole d’un côté et la réalité des implémentations des clients de l’autre.
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 j’aurai un truc à peu prêt fonctionnel, il servira d’inspiration pour reprendre le reste du projet proprement.
2024-09-17
Cela fait un bon moment que je n’ai pas avancé sur ce projet, et c'est en partie lié à un problème que j’avais 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 j’essayais d’implémenter la dernière version disponible, la 1.9, il me signalait qu'elle était trop ancienne et ne fonctionnait correctement que lorsque j’annonç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 qu’il 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 n’a reçu une réponse qu’en 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 l’environnement de développement, phpactor ça rame un peu, voir beaucoup. L’auto-completion, si ça doit faire perdre 2 secondes à chaque fois qu’on veut écrire un truc, c’est 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 j’ai trouvée est de rajouter ces outils à la liste de packages de l’utilisateur. 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 d’autres. 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 qu’il 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 l’environnement 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 m’aider à 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
J’ai un peu avancé hier sur l’authentification. 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 n’est 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 j’utilise 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 d’accueil est trop
ancien et ne prend pas en
charge la version minimale
requise de l’API.
C’est un peu fort de café.
Je n’arrive pas non plus à savoir quelle version, element attend. Ça commence à devenir pénible. Au moins, côté fluffy chat, l’application indique les choses clairement :
Le serveur d’accueil 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 j’ai dû recopier cette phrase à la main. Ça semble à la mode d’avoir 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 qu’en avançant sur l’implémentation, Element se mettra à accepter mon serveur. En attendant, j’aurai au moins fluffy chat qui pourra fonctionner ou me donner des logs pertinents.
2023-12-11
J’ai hâte de commencer à coder mais, malheureusement, je tiens à mettre en place toutes les bonnes pratiques que j’attends d’un projet. Entre autre, le système d’intégration continue, avec une analyse de la qualité de code avant de commencer à produire.
J’ai déjà récupéré un fichier shell.nix d’un 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 n’est 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 n’y 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 d’avancer 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 l’on lance l’application web, on a trois requêtes qui partent d’affilée.
- Un
GET https://matrix.org/.well-known/matrix/clientdont 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"
}
}
- Un
POST https://matrix-client.matrix.org/_matrix/client/r0/registerdont 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"
}
}
}
}
}
}
- Un
GET https://matrix-client.matrix.org/_matrix/client/r0/logindont 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/versionsavec l’en-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 n’appartiennent 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/loginGET https://matrix-client.matrix.org/_matrix/client/r0/login
Pourquoi tester à la fois sur v3 et r0 ? Aucune idée… D’autant 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. J’ai 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 d’une version LTS de Symfony. Symfony car je fais le choix arbitraire d’utiliser 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.