feat(android): show first, sync after
The local vault now opens by itself at launch and the GET runs behind it, silently. A 304 shows nothing; a network failure shows nothing either - offline is a normal state, and an alert on every launch only teaches people to dismiss alerts. Only a new snapshot manifests, by refreshing the list. That silence is what puts the sync password inside the Keystore-encrypted file next to the vault: without it a new snapshot cannot be opened, so every edit made on the desktop would raise a password prompt at launch. The file already holds every password in plaintext; this one adds almost nothing to its exposure. A typed password still wins over the stored one, which is how you fix one that no longer works. Adopting a snapshot while the app is already open rebuilds the list in place instead of navigating - mandatory even from the detail screen, since list rows carry indices into the old vault and a tap would open the wrong entry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+72
-22
@@ -75,6 +75,10 @@ type
|
||||
// economiser un telechargement pour se retrouver sans donnees.
|
||||
FETag: string;
|
||||
FBusy: Boolean;
|
||||
// Mot de passe de sync tel qu'il a ete range avec le coffre. Il rend le
|
||||
// rafraichissement de fond silencieux : sans lui, chaque modification
|
||||
// faite sur le poste ferait apparaitre une invite au lancement.
|
||||
FSyncPwd: string;
|
||||
|
||||
// --- ecran 1 : deverrouillage
|
||||
FLayUnlock: TLayout;
|
||||
@@ -126,7 +130,11 @@ type
|
||||
// procedure imbriquee (E2555), et le rappel de la biometrie en est une.
|
||||
procedure OpenLocalNow;
|
||||
procedure DoSync(Sender: TObject);
|
||||
procedure FetchAndDecrypt(const AUrl, ATok, APwd: string);
|
||||
/// ASilent : rafraichissement de fond. Le coffre local est deja affiche,
|
||||
/// donc un echec reseau ne doit rien reclamer a l'utilisateur - il garde
|
||||
/// ce qu'il a. Seul un instantane NOUVEAU se voit.
|
||||
procedure FetchAndDecrypt(const AUrl, ATok, APwd: string;
|
||||
ASilent: Boolean = False);
|
||||
procedure AdoptVault(const APlainJson: string);
|
||||
procedure LockVault;
|
||||
|
||||
@@ -202,6 +210,12 @@ begin
|
||||
LEvents.SetApplicationEventHandler(HandleAppEvent);
|
||||
|
||||
ShowUnlock;
|
||||
|
||||
// Ouverture automatique du coffre local. Differee apres la construction :
|
||||
// l'invite biometrique s'afficherait sinon avant la premiere peinture de la
|
||||
// fenetre, sur un fond vide.
|
||||
if HasVault then
|
||||
TThread.ForceQueue(nil, procedure begin DoOpenLocal(nil); end);
|
||||
end;
|
||||
|
||||
destructor TForm8.Destroy;
|
||||
@@ -635,18 +649,31 @@ begin
|
||||
FVault.LoadFromJson(APlainJson);
|
||||
Log(' ' + FVault.LiveCount.ToString + ' entrees, ' +
|
||||
FVault.TrashCount.ToString + ' en corbeille');
|
||||
ShowList;
|
||||
// Deja dans l'application : reconstruire la liste SANS y ramener
|
||||
// l'utilisateur. Obligatoire meme s'il est dans le detail - les lignes de
|
||||
// la liste portent des indices vers l'ancien coffre, et un appui ouvrirait
|
||||
// la mauvaise entree.
|
||||
if FLayList.Visible or FLayDetail.Visible then
|
||||
OnSearchChanged(nil)
|
||||
else
|
||||
ShowList;
|
||||
end;
|
||||
|
||||
procedure TForm8.OpenLocalNow;
|
||||
var
|
||||
LPlain, LETag: string;
|
||||
LPlain, LETag, LPwd: string;
|
||||
begin
|
||||
if LoadVault(LPlain, LETag) then
|
||||
if LoadVault(LPlain, LETag, LPwd) then
|
||||
begin
|
||||
FETag := LETag;
|
||||
if LPwd <> '' then FSyncPwd := LPwd;
|
||||
try
|
||||
AdoptVault(LPlain);
|
||||
// Afficher d'abord, synchroniser ensuite : le coffre est a l'ecran, le
|
||||
// reseau se debrouille derriere. Sans mot de passe range, on ne peut
|
||||
// rien ouvrir de nouveau - inutile d'aller voir.
|
||||
if FSyncPwd <> '' then
|
||||
FetchAndDecrypt(FUrl.Text.Trim, FToken.Text.Trim, FSyncPwd, True);
|
||||
except
|
||||
on E: Exception do
|
||||
begin
|
||||
@@ -705,7 +732,10 @@ begin
|
||||
// thread de travail est un plantage differe, pas une erreur de compilation.
|
||||
LUrl := FUrl.Text.Trim;
|
||||
LTok := FToken.Text.Trim;
|
||||
// Ce qui est tape l'emporte : c'est ainsi qu'on corrige un mot de passe
|
||||
// range qui ne convient plus.
|
||||
LPwd := FPwd.Text;
|
||||
if LPwd = '' then LPwd := FSyncPwd;
|
||||
if LPwd = '' then
|
||||
begin
|
||||
Status('Mot de passe de sync requis');
|
||||
@@ -715,21 +745,22 @@ begin
|
||||
FetchAndDecrypt(LUrl, LTok, LPwd);
|
||||
end;
|
||||
|
||||
procedure TForm8.FetchAndDecrypt(const AUrl, ATok, APwd: string);
|
||||
procedure TForm8.FetchAndDecrypt(const AUrl, ATok, APwd: string;
|
||||
ASilent: Boolean);
|
||||
begin
|
||||
SetBusy(True);
|
||||
Status('Telechargement...');
|
||||
if not ASilent then Status('Telechargement...');
|
||||
TThread.CreateAnonymousThread(
|
||||
procedure
|
||||
var
|
||||
LClient: TSyncClient;
|
||||
LRes: TSyncResult;
|
||||
LPlain, LCached, LCachedETag: string;
|
||||
LPlain, LCached, LCachedETag, LCachedPwd: string;
|
||||
LSw: TStopwatch;
|
||||
begin
|
||||
// Le coffre local d'abord : s'il est la, on connait deja l'ETag et on
|
||||
// evite les 4 secondes de derivation quand rien n'a change.
|
||||
if (FETag = '') and LoadVault(LCached, LCachedETag) then
|
||||
if (FETag = '') and LoadVault(LCached, LCachedETag, LCachedPwd) then
|
||||
FETag := LCachedETag;
|
||||
|
||||
LClient := TSyncClient.Create(AUrl, ATok);
|
||||
@@ -760,10 +791,11 @@ begin
|
||||
procedure
|
||||
begin
|
||||
if FVault.Count > 0 then
|
||||
begin
|
||||
Log(' 304 : inchange, coffre deja ouvert');
|
||||
ShowList;
|
||||
end
|
||||
// Rien a faire : ce qui est affiche est deja a jour. En
|
||||
// rafraichissement de fond c'est le cas courant, et il
|
||||
// doit passer inapercu - surtout pas de retour a la liste
|
||||
// alors que l'utilisateur consulte une entree.
|
||||
Log(' 304 : inchange, coffre deja ouvert')
|
||||
else if LCached <> '' then
|
||||
begin
|
||||
Log(' 304 : inchange, coffre servi depuis le cache');
|
||||
@@ -778,7 +810,8 @@ begin
|
||||
// Un ETag sans clair correspondant : l'invariant est rompu.
|
||||
// On le jette pour que l'appel suivant retelecharge.
|
||||
FETag := '';
|
||||
Status('Cache absent - appuyer a nouveau pour telecharger');
|
||||
if not ASilent then
|
||||
Status('Cache absent - appuyer a nouveau pour telecharger');
|
||||
end;
|
||||
end);
|
||||
Exit;
|
||||
@@ -787,21 +820,27 @@ begin
|
||||
begin
|
||||
// Le serveur repond pareil dans les deux cas, par conception.
|
||||
Log(' 404 : pas d''instantane OU jeton invalide');
|
||||
TThread.Queue(nil, procedure
|
||||
begin Status('Rien a telecharger (404)'); end);
|
||||
if not ASilent then
|
||||
TThread.Queue(nil, procedure
|
||||
begin Status('Rien a telecharger (404)'); end);
|
||||
Exit;
|
||||
end;
|
||||
ssRefused, ssHttpError, ssNetworkError:
|
||||
begin
|
||||
// En fond, un echec reseau ne se signale pas : le coffre local
|
||||
// est affiche, hors ligne est un etat normal, et une alerte ici
|
||||
// apprendrait a l'utilisateur a ignorer les alertes.
|
||||
Log(' echec : ' + LRes.Error);
|
||||
TThread.Queue(nil, procedure
|
||||
begin Status('Echec reseau'); end);
|
||||
if not ASilent then
|
||||
TThread.Queue(nil, procedure
|
||||
begin Status('Echec reseau'); end);
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
Log(' recu ' + Length(LRes.Container).ToString + ' octets');
|
||||
TThread.Queue(nil, procedure begin Status('Dechiffrement...'); end);
|
||||
if not ASilent then
|
||||
TThread.Queue(nil, procedure begin Status('Dechiffrement...'); end);
|
||||
|
||||
LSw := TStopwatch.StartNew;
|
||||
// Volontairement AVANT de retenir l'ETag : un echec ici doit laisser
|
||||
@@ -817,7 +856,9 @@ begin
|
||||
// True : on DEMANDE la protection par authentification. PMA.Store
|
||||
// retombe tout seul sur une cle simple si l'appareil n'a ni
|
||||
// empreinte ni code, plutot que de renoncer au cache.
|
||||
SaveVault(LPlain, FETag, True);
|
||||
// Le mot de passe part avec le coffre : c'est lui qui permettra au
|
||||
// prochain lancement de rafraichir sans rien demander.
|
||||
SaveVault(LPlain, FETag, APwd, True);
|
||||
if VaultNeedsAuth then
|
||||
Log(' cache ecrit - protege par biometrie/code')
|
||||
else
|
||||
@@ -832,10 +873,13 @@ begin
|
||||
TThread.Queue(nil,
|
||||
procedure
|
||||
begin
|
||||
FSyncPwd := APwd; // il vient d'ouvrir un conteneur : il est bon
|
||||
try
|
||||
AdoptVault(LPlain);
|
||||
except
|
||||
on E: Exception do Status('Instantane illisible : ' + E.Message);
|
||||
on E: Exception do
|
||||
if not ASilent then
|
||||
Status('Instantane illisible : ' + E.Message);
|
||||
end;
|
||||
end);
|
||||
except
|
||||
@@ -844,8 +888,14 @@ begin
|
||||
Log('ERREUR ' + E.ClassName + ' : ' + E.Message);
|
||||
// Le cas de loin le plus frequent, et le message par defaut ne le
|
||||
// dit pas : GCM echoue a la verification quand la cle est fausse.
|
||||
TThread.Queue(nil, procedure
|
||||
begin Status('Echec - mot de passe de sync incorrect ?'); end);
|
||||
//
|
||||
// En fond, cela veut dire que le mot de passe range ne convient
|
||||
// plus - le poste a change de mot de passe de sync. On ne le jette
|
||||
// PAS : il faudra le retaper, mais garder l'ancien laisse au moins
|
||||
// le champ prerempli, et le coffre local reste consultable.
|
||||
if not ASilent then
|
||||
TThread.Queue(nil, procedure
|
||||
begin Status('Echec - mot de passe de sync incorrect ?'); end);
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
|
||||
Reference in New Issue
Block a user