feat(sync): le QR porte un lien pmserver://sync ouvert directement par l'app
Le scanner du telephone envoyait le texte a une recherche web (Google, Chrome) : le jeton serait parti a un appui pres. Le lien a schema propre n'a qu'une application pour l'ouvrir. Rien n'est enregistre avant Save. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -93,6 +93,14 @@
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<!-- Lien pmserver://sync?u=&t= du QR affiche par le poste : le
|
||||
scanner du telephone l'ouvre directement dans l'app. -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="pmserver" android:host="sync" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- FMX n'affiche pas BiometricPrompt depuis notre NativeActivity : il
|
||||
lance SA PROPRE activite, qui heberge le fragment androidx. Elle
|
||||
|
||||
@@ -1336,8 +1336,10 @@ begin
|
||||
// ne transite pas en clair par le moteur de rendu (meme raison que
|
||||
// prefs/has). Le mot de passe de synchro n'y est JAMAIS : un QR se
|
||||
// photographie par-dessus l'epaule.
|
||||
// JSON nu, sans prefixe "pmsync:" : un prefixe a deux-points se lit comme un
|
||||
// lien, et l'appareil photo proposait "Ouvrir avec" au lieu de copier.
|
||||
// UN LIEN pmserver://sync?u=&t= ET NON DU TEXTE. Le scanner du telephone
|
||||
// envoie tout texte a une recherche web (Google, Chrome) - le jeton serait
|
||||
// parti chez Google a un appui pres. Un lien a schema propre n'a qu'une
|
||||
// application pour l'ouvrir : la notre.
|
||||
else if ACmd = 'sync/qr' then
|
||||
begin
|
||||
var LQrUrl := PM.UserPrefs.GetPref('syncUrl');
|
||||
@@ -1346,16 +1348,13 @@ begin
|
||||
var LSize := 0;
|
||||
if (LQrUrl <> '') and (LQrTok <> '') then
|
||||
begin
|
||||
var LJson := TJSONObject.Create;
|
||||
var LQr := TDelphiZXingQRCode.Create;
|
||||
var LSb := TStringBuilder.Create;
|
||||
try
|
||||
LJson.AddPair('pmsync', TJSONNumber.Create(1));
|
||||
LJson.AddPair('u', LQrUrl);
|
||||
LJson.AddPair('t', LQrTok);
|
||||
LQr.Encoding := qrUTF8NoBOM;
|
||||
LQr.QuietZone := 4;
|
||||
LQr.Data := LJson.ToJSON;
|
||||
LQr.Data := 'pmserver://sync?u=' + TNetEncoding.URL.Encode(LQrUrl) +
|
||||
'&t=' + TNetEncoding.URL.Encode(LQrTok);
|
||||
LSize := LQr.Rows;
|
||||
for var R := 0 to LQr.Rows - 1 do
|
||||
for var C := 0 to LQr.Columns - 1 do
|
||||
@@ -1364,7 +1363,6 @@ begin
|
||||
finally
|
||||
LSb.Free;
|
||||
LQr.Free;
|
||||
LJson.Free;
|
||||
end;
|
||||
end;
|
||||
if LSize > 0 then
|
||||
|
||||
+3
-3
@@ -65,9 +65,9 @@ Bridge.onSyncQr = function (size, bits) {
|
||||
'background:#fff;display:block;border-radius:6px">' +
|
||||
'<path d="' + d + '" fill="#000"/></svg>' +
|
||||
'<p style="font-size:11px;color:var(--text-faint);margin:6px 0 0">' +
|
||||
'Scan it with the phone camera and copy the text, then in the app: ' +
|
||||
'Settings → Paste settings. It holds the URL and the token, not the ' +
|
||||
'sync password. Hidden again in 60 s.</p>';
|
||||
'Scan it with the phone camera and open it with Coffre: the URL and the ' +
|
||||
'token fill in, then type the sync password (never in the QR) and Save. ' +
|
||||
'Hidden again in 60 s.</p>';
|
||||
box.hidden = false;
|
||||
clearTimeout(syncQrTimer);
|
||||
syncQrTimer = setTimeout(syncQrHide, 60000);
|
||||
|
||||
+62
-21
@@ -36,7 +36,7 @@ uses
|
||||
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Platform,
|
||||
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Memo, FMX.Memo.Types,
|
||||
FMX.ScrollBox, FMX.Layouts, FMX.Edit, FMX.ListBox, FMX.Objects,
|
||||
FMX.WebBrowser,
|
||||
FMX.WebBrowser, System.Messaging,
|
||||
PMA.Vault;
|
||||
|
||||
const
|
||||
@@ -342,6 +342,10 @@ type
|
||||
procedure SetUnlockCode(const ANew: string);
|
||||
procedure PushUnlockMode;
|
||||
procedure PasteSyncSettings;
|
||||
function ApplySyncLink(const ALink: string): Boolean;
|
||||
{$IFDEF ANDROID}
|
||||
procedure OnActivityMessage(const Sender: TObject; const M: TMessage);
|
||||
{$ENDIF}
|
||||
function PwdName: string;
|
||||
/// Va chercher l'icone du site de l'entree ouverte, ou l'efface.
|
||||
procedure SetEntryIcon(AFetch: Boolean);
|
||||
@@ -381,7 +385,11 @@ implementation
|
||||
uses
|
||||
PMA.Crypto, PMA.SyncClient, PMA.Store, PMA.Biometric, PMA.Totp,
|
||||
PMA.ScreenGuard, PMA.Clipboard, PMA.Theme, PMA.WebUI, PMA.VaultPatch,
|
||||
PMA.SaveFile, PMA.Favicon, System.JSON;
|
||||
PMA.SaveFile, PMA.Favicon, System.JSON
|
||||
{$IFDEF ANDROID}
|
||||
, Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.JavaTypes,
|
||||
Androidapi.Helpers, FMX.Platform.Android
|
||||
{$ENDIF};
|
||||
|
||||
{$R *.fmx}
|
||||
|
||||
@@ -668,6 +676,18 @@ begin
|
||||
FWeb.OnDidFinishLoad := OnWebLoaded;
|
||||
FWeb.OnShouldStartLoadWithRequest := OnWebNavigate;
|
||||
FWeb.LoadFromStrings(PageHtml, '');
|
||||
|
||||
{$IFDEF ANDROID}
|
||||
// LIEN DU QR DU POSTE. Au lancement par le lien, il arrive dans l'intent de
|
||||
// depart ; app deja ouverte (singleTask), il arrive par message. WebCall met
|
||||
// en attente tant que la page n'est pas chargee.
|
||||
MainActivity.registerIntentAction(TJIntent.JavaClass.ACTION_VIEW);
|
||||
TMessageManager.DefaultManager.SubscribeToMessage(TMessageReceivedNotification,
|
||||
OnActivityMessage);
|
||||
if (TAndroidHelper.Activity.getIntent <> nil) and
|
||||
(TAndroidHelper.Activity.getIntent.getDataString <> nil) then
|
||||
ApplySyncLink(JStringToString(TAndroidHelper.Activity.getIntent.getDataString));
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
procedure TForm8.OnWebLoaded(ASender: TObject);
|
||||
@@ -3206,33 +3226,54 @@ begin
|
||||
end;
|
||||
|
||||
procedure TForm8.PasteSyncSettings;
|
||||
var
|
||||
LDoc: TJSONObject;
|
||||
LUrl, LTok: string;
|
||||
begin
|
||||
if ApplySyncLink(CurrentClipboard.Trim) then
|
||||
ClearClipboard
|
||||
else
|
||||
WebCall('PM.toast("No settings in the clipboard - copy the link of the QR shown on the PC first")');
|
||||
end;
|
||||
|
||||
/// pmserver://sync?u=URL&t=JETON -> champs des reglages remplis, RIEN
|
||||
/// d'enregistre. Le lien peut venir de n'importe quelle page ou application :
|
||||
/// c'est Save, geste de l'utilisateur devant l'URL affichee, qui valide.
|
||||
function TForm8.ApplySyncLink(const ALink: string): Boolean;
|
||||
var
|
||||
LPair: string;
|
||||
LUrl, LTok, LName, LValue: string;
|
||||
LEq: Integer;
|
||||
begin
|
||||
Result := False;
|
||||
if not StartsText('pmserver://sync?', ALink) then Exit;
|
||||
LUrl := '';
|
||||
LTok := '';
|
||||
LDoc := TJSONObject.ParseJSONValue(CurrentClipboard.Trim) as TJSONObject;
|
||||
if LDoc <> nil then
|
||||
try
|
||||
if LDoc.GetValue<Integer>('pmsync', 0) = 1 then
|
||||
for LPair in Copy(ALink, Length('pmserver://sync?') + 1, MaxInt).Split(['&']) do
|
||||
begin
|
||||
LUrl := LDoc.GetValue<string>('u', '');
|
||||
LTok := LDoc.GetValue<string>('t', '');
|
||||
LEq := Pos('=', LPair);
|
||||
if LEq = 0 then Continue;
|
||||
LName := Copy(LPair, 1, LEq - 1);
|
||||
LValue := TNetEncoding.URL.Decode(Copy(LPair, LEq + 1, MaxInt));
|
||||
if LName = 'u' then LUrl := LValue
|
||||
else if LName = 't' then LTok := LValue;
|
||||
end;
|
||||
finally
|
||||
LDoc.Free;
|
||||
end;
|
||||
if (LUrl = '') or (LTok = '') then
|
||||
begin
|
||||
WebCall('PM.toast("No settings in the clipboard - scan the QR on the PC and copy its text first")');
|
||||
Exit;
|
||||
end;
|
||||
ClearClipboard;
|
||||
Log('Sync settings pasted from the clipboard: token ' + Fingerprint(LTok));
|
||||
if (LUrl = '') or (LTok = '') then Exit;
|
||||
Result := True;
|
||||
Log('Sync settings received from a link: token ' + Fingerprint(LTok));
|
||||
WebCall('openSettings()');
|
||||
WebCall(Format('PM.pasteConfig(%s, %s)', [JsArg(LUrl), JsArg(LTok)]));
|
||||
end;
|
||||
|
||||
{$IFDEF ANDROID}
|
||||
procedure TForm8.OnActivityMessage(const Sender: TObject; const M: TMessage);
|
||||
var
|
||||
LIntent: JIntent;
|
||||
begin
|
||||
if not (M is TMessageReceivedNotification) then Exit;
|
||||
LIntent := TMessageReceivedNotification(M).Value;
|
||||
if (LIntent <> nil) and (LIntent.getDataString <> nil) then
|
||||
ApplySyncLink(JStringToString(LIntent.getDataString));
|
||||
end;
|
||||
{$ENDIF}
|
||||
|
||||
function TForm8.CachePwd: string;
|
||||
begin
|
||||
if HasLocalContainer then Result := FVaultPwd else Result := FSyncPwd;
|
||||
|
||||
Reference in New Issue
Block a user