Ouvrir l’app
Détails techniques

Hors ligne d’abord, chiffré de bout en bout.

Une seule interface pour le web, iOS et Android, qui lit et écrit toujours sur l’appareil. Un serveur qui synchronise, sauvegarde et fait vivre le social, sans jamais pouvoir lire tes messages.

Architecture

L’écran ne attend jamais le réseau.

L’interface écrit dans la base locale de l’appareil, puis une couche connectée envoie les différences au serveur et récupère celles des autres appareils.

Appareil

Interface PepsGoalHTML, CSS et JavaScript sans framework, identique sur web, iOS et Android
Données localesStockage de la page, copie complète pour le hors ligne
Couche connectée · cloud.jsComptes, synchro, social, messages chiffrés, modération
Surveillance réseaunavigator.onLine + /v1/status : 5 s, 10 s, 20 s, 40 s puis chaque minute

Serveur

CaddyHTTPS automatique (Let’s Encrypt), compression, en-têtes de sécurité
API Fastify 4 + zodPrès de 90 routes, chaque entrée validée par un schéma partagé
PostgreSQL 16Schéma et migrations Drizzle, révisions de synchro, cascades RGPD
MédiasPhotos et pièces jointes chiffrées, sur disque ou S3
SituationObjectifs, validations, sériesFeed, amis, messagesSynchronisation
Compte + en ligne✓✓Automatique : démarrage, retour au premier plan, chaque minute
Compte + hors ligne✓ base localeMasqués temporairementFile d’attente, envoyée au retour de la connexion
Sans compte✓ base localeIndisponiblesTout est conservé si un compte est créé plus tard
Maintenance sociale✓Lecture seule✓
Maintenance complète✓ base localeMasquésSuspendue (503), sans casser la série
La pile

Des briques éprouvées.

Interface
HTML · CSS · JS

Une seule interface sans framework, de tous les écrans aux animations : rapide, légère, identique partout.

Mobile
Expo SDK 51

Coque native React Native 0.74 + WebView qui embarque l’interface hors ligne. Builds et publication via EAS.

API
Fastify 4 + zod

TypeScript, Helmet, CORS, limitation de débit, multipart. Schémas partagés avec l’app dans packages/shared.

Base
PostgreSQL 16

Drizzle pour le schéma et les migrations, postgres.js pour des requêtes paramétrées : aucune injection SQL possible.

Chiffrement
TweetNaCl

Implémentation auditée de NaCl : X25519, XSalsa20-Poly1305. PBKDF2-SHA-256 pour le coffre de clés.

Comptes
bcrypt + JWT

Mots de passe bcrypt 12 tours ; jeton d’accès 15 min et jeton de renouvellement 30 jours à usage unique.

Hébergement
Docker + Caddy

Un VPS Hostinger dans l’Union européenne, Docker Compose, HTTPS automatique, sauvegardes quotidiennes.

Données
JSZip

Export et import de ton compte en .zip, clés de conversation comprises, directement sur l’appareil.

Qualité
Vitest + TypeScript

Tests du protocole et du chiffrement, scénario de bout en bout d’une quarantaine de vérifications contre un serveur vierge.

Synchronisation

La plus récente gagne.

Une règle unique, partagée par le serveur et l’app (resolveLww), et des opérations qui peuvent être renvoyées sans danger.

Écrire tout de suite

Chaque action est écrite immédiatement dans la base locale, puis ajoutée à une file d’attente. Les identifiants sont des UUID générés par l’appareil : un objectif créé hors ligne garde son identifiant.

Envoyer par lots

POST /v1/sync/push par lots de 100. Chaque opération a sa transaction ; un opId déjà reçu n’est jamais appliqué deux fois.

Recevoir le reste

GET /v1/sync/pull?cursor=N renvoie tout ce qui a changé depuis la révision N, par pages de 500. Le curseur n’avance qu’après application.

// une opération de la file d’attente { "opId": "9b1c…e42f", "entity": "checkin", "action": "upsert", "id": "3f0a…77d1", "data": { "goalId": "…", "day": "2026-10-12" }, "clientUpdatedAt": "2026-10-12T08:15:00.000Z" }

Conflits et refus

  • Le serveur refuse une opération plus ancienne que sa version (stale).
  • L’app n’écrase pas une modification locale plus récente encore dans la file.
  • Une horloge d’appareil en avance est ramenée à « maintenant + 5 minutes ».
  • Autres refus : forbidden, invalid, not_found ; la version du serveur est alors récupérée.
  • La déconnexion est bloquée tant que des modifications attendent : rien ne se perd.
Chiffrement de bout en bout

Le serveur transporte, sans pouvoir lire.

Messages privés et groupes sont chiffrés sur l’appareil avant l’envoi. Seuls les messages du compte PepsGoal Officiel circulent en clair.

Connexion

L’appareil génère sa paire X25519 et publie sa clé publique.

Clé de paire

32 octets aléatoires, tirés par le premier qui écrit.

Enveloppes

La clé est chiffrée pour chaque appareil (crypto_box).

Envoi

Texte scellé avec crypto_secretbox ; le serveur refuse tout message en clair.

Nouvel appareil

Les appareils qui détiennent la clé la lui transmettent.

Ce que voit le serveur

{ v: 1, keyId, nonce, ct: }

Illustration : un brouillage aléatoire calculé dans ton navigateur, pas le vrai chiffrement de l’app.

Coffre, groupes et pièces jointes

  • Coffre de clés : une copie de tes clés, chiffrée avec une clé dérivée de ton mot de passe (PBKDF2-SHA-256, 310 000 itérations). Un nouvel appareil retrouve tout l’historique.
  • Groupes : une clé par groupe, en versions ; un membre qui part ne reçoit pas la suivante.
  • Photos et vocaux : chiffrés avec une clé à usage unique qui voyage dans le message chiffré.
  • Code de sécurité de 30 chiffres, calculé sur les clés publiques de tous les appareils.
  • Limites assumées : les métadonnées (qui, quand, quelle taille) restent visibles par le serveur.
Sécurité et vie privée

Prudent par défaut.

Validation stricte

Chaque entrée passe un schéma zod ; les données inconnues sont refusées.

Points plafonnés côté serveur

Un client modifié ne peut pas gonfler les classements.

Jetons à usage unique

Un jeton de renouvellement volé et réutilisé révoque toutes les sessions du compte.

Images vérifiées

8 Mo maximum, type contrôlé par la signature du fichier (JPEG, PNG, WebP), nom aléatoire.

Limitation de débit

300 requêtes par minute, renforcée sur connexion, inscription, publication et signalements.

Suppression en cascade

Supprimer son compte efface toutes ses données ; les sauvegardes suivent sous 14 jours.

Aucun traceur

Pas de publicité, pas de mesure d’audience. Polices et bibliothèques intégrées : aucun service tiers contacté.

Modération outillée

Signalements, sanctions graduées, contestations, journal des décisions et alertes par e-mail.

Le code

Un monorepo, une source de vérité.

Les 14 types d’objectifs et leurs réglages sont définis une seule fois dans packages/shared et validés à l’identique par le serveur et l’app.

pepsgoal-platform/ ├── packages/shared/ modèle, zod, synchro, séries, E2E ├── apps/server/ API Fastify + PostgreSQL ├── apps/web-legacy/ l’interface : tous les écrans ├── apps/web/ cloud.js, polices, emojis, build ├── apps/app/ coque iOS / Android (Expo) ├── deploy/ Caddy, Docker, ce site └── docs/ architecture, API, E2E, stores

Personnel, synchronisé

goalscheckinsgoal_groupsusers.settings

Partagé

goal_membersgoals.buddy_id

Social

friendshipsblockspostspost_likescommentsstoriesstory_viewscheer_creditsnotifications

Messagerie

messageschat_groupsattachmentse2e_devicese2e_envelopese2e_vaults

Modération

reportsappealsmod_actionsmaintenanceannouncements
Versions

Le journal des nouveautés.

1.2.0 actuelle 12 octobre 2026

  • Messagerie enrichie : photos, messages vocaux, objectifs partagés, groupes de discussion.
  • Clés de chiffrement synchronisées entre appareils (coffre).
  • Photos dans les publications, blocage entre utilisateurs, e-mails de modération, emojis Fluent.

1.1.0 11 octobre 2026

  • Mode hors ligne automatique et surveillance du réseau.
  • Options d’accessibilité : contraste élevé, daltonisme, transparence, animations.
  • Messagerie chiffrée de bout en bout.

1.0.0 10 octobre 2026

  • Serveur de synchronisation, comptes, fonctions sociales et modération.
  • Application web, iOS et Android à partir d’une même interface.