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" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</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>
|
</activity>
|
||||||
<!-- FMX n'affiche pas BiometricPrompt depuis notre NativeActivity : il
|
<!-- FMX n'affiche pas BiometricPrompt depuis notre NativeActivity : il
|
||||||
lance SA PROPRE activite, qui heberge le fragment androidx. Elle
|
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
|
// 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
|
// prefs/has). Le mot de passe de synchro n'y est JAMAIS : un QR se
|
||||||
// photographie par-dessus l'epaule.
|
// photographie par-dessus l'epaule.
|
||||||
// JSON nu, sans prefixe "pmsync:" : un prefixe a deux-points se lit comme un
|
// UN LIEN pmserver://sync?u=&t= ET NON DU TEXTE. Le scanner du telephone
|
||||||
// lien, et l'appareil photo proposait "Ouvrir avec" au lieu de copier.
|
// 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
|
else if ACmd = 'sync/qr' then
|
||||||
begin
|
begin
|
||||||
var LQrUrl := PM.UserPrefs.GetPref('syncUrl');
|
var LQrUrl := PM.UserPrefs.GetPref('syncUrl');
|
||||||
@@ -1346,16 +1348,13 @@ begin
|
|||||||
var LSize := 0;
|
var LSize := 0;
|
||||||
if (LQrUrl <> '') and (LQrTok <> '') then
|
if (LQrUrl <> '') and (LQrTok <> '') then
|
||||||
begin
|
begin
|
||||||
var LJson := TJSONObject.Create;
|
|
||||||
var LQr := TDelphiZXingQRCode.Create;
|
var LQr := TDelphiZXingQRCode.Create;
|
||||||
var LSb := TStringBuilder.Create;
|
var LSb := TStringBuilder.Create;
|
||||||
try
|
try
|
||||||
LJson.AddPair('pmsync', TJSONNumber.Create(1));
|
|
||||||
LJson.AddPair('u', LQrUrl);
|
|
||||||
LJson.AddPair('t', LQrTok);
|
|
||||||
LQr.Encoding := qrUTF8NoBOM;
|
LQr.Encoding := qrUTF8NoBOM;
|
||||||
LQr.QuietZone := 4;
|
LQr.QuietZone := 4;
|
||||||
LQr.Data := LJson.ToJSON;
|
LQr.Data := 'pmserver://sync?u=' + TNetEncoding.URL.Encode(LQrUrl) +
|
||||||
|
'&t=' + TNetEncoding.URL.Encode(LQrTok);
|
||||||
LSize := LQr.Rows;
|
LSize := LQr.Rows;
|
||||||
for var R := 0 to LQr.Rows - 1 do
|
for var R := 0 to LQr.Rows - 1 do
|
||||||
for var C := 0 to LQr.Columns - 1 do
|
for var C := 0 to LQr.Columns - 1 do
|
||||||
@@ -1364,7 +1363,6 @@ begin
|
|||||||
finally
|
finally
|
||||||
LSb.Free;
|
LSb.Free;
|
||||||
LQr.Free;
|
LQr.Free;
|
||||||
LJson.Free;
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
if LSize > 0 then
|
if LSize > 0 then
|
||||||
|
|||||||
+3
-3
@@ -65,9 +65,9 @@ Bridge.onSyncQr = function (size, bits) {
|
|||||||
'background:#fff;display:block;border-radius:6px">' +
|
'background:#fff;display:block;border-radius:6px">' +
|
||||||
'<path d="' + d + '" fill="#000"/></svg>' +
|
'<path d="' + d + '" fill="#000"/></svg>' +
|
||||||
'<p style="font-size:11px;color:var(--text-faint);margin:6px 0 0">' +
|
'<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: ' +
|
'Scan it with the phone camera and open it with Coffre: the URL and the ' +
|
||||||
'Settings → Paste settings. It holds the URL and the token, not the ' +
|
'token fill in, then type the sync password (never in the QR) and Save. ' +
|
||||||
'sync password. Hidden again in 60 s.</p>';
|
'Hidden again in 60 s.</p>';
|
||||||
box.hidden = false;
|
box.hidden = false;
|
||||||
clearTimeout(syncQrTimer);
|
clearTimeout(syncQrTimer);
|
||||||
syncQrTimer = setTimeout(syncQrHide, 60000);
|
syncQrTimer = setTimeout(syncQrHide, 60000);
|
||||||
|
|||||||
+62
-21
@@ -36,7 +36,7 @@ uses
|
|||||||
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Platform,
|
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Platform,
|
||||||
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Memo, FMX.Memo.Types,
|
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Memo, FMX.Memo.Types,
|
||||||
FMX.ScrollBox, FMX.Layouts, FMX.Edit, FMX.ListBox, FMX.Objects,
|
FMX.ScrollBox, FMX.Layouts, FMX.Edit, FMX.ListBox, FMX.Objects,
|
||||||
FMX.WebBrowser,
|
FMX.WebBrowser, System.Messaging,
|
||||||
PMA.Vault;
|
PMA.Vault;
|
||||||
|
|
||||||
const
|
const
|
||||||
@@ -342,6 +342,10 @@ type
|
|||||||
procedure SetUnlockCode(const ANew: string);
|
procedure SetUnlockCode(const ANew: string);
|
||||||
procedure PushUnlockMode;
|
procedure PushUnlockMode;
|
||||||
procedure PasteSyncSettings;
|
procedure PasteSyncSettings;
|
||||||
|
function ApplySyncLink(const ALink: string): Boolean;
|
||||||
|
{$IFDEF ANDROID}
|
||||||
|
procedure OnActivityMessage(const Sender: TObject; const M: TMessage);
|
||||||
|
{$ENDIF}
|
||||||
function PwdName: string;
|
function PwdName: string;
|
||||||
/// Va chercher l'icone du site de l'entree ouverte, ou l'efface.
|
/// Va chercher l'icone du site de l'entree ouverte, ou l'efface.
|
||||||
procedure SetEntryIcon(AFetch: Boolean);
|
procedure SetEntryIcon(AFetch: Boolean);
|
||||||
@@ -381,7 +385,11 @@ implementation
|
|||||||
uses
|
uses
|
||||||
PMA.Crypto, PMA.SyncClient, PMA.Store, PMA.Biometric, PMA.Totp,
|
PMA.Crypto, PMA.SyncClient, PMA.Store, PMA.Biometric, PMA.Totp,
|
||||||
PMA.ScreenGuard, PMA.Clipboard, PMA.Theme, PMA.WebUI, PMA.VaultPatch,
|
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}
|
{$R *.fmx}
|
||||||
|
|
||||||
@@ -668,6 +676,18 @@ begin
|
|||||||
FWeb.OnDidFinishLoad := OnWebLoaded;
|
FWeb.OnDidFinishLoad := OnWebLoaded;
|
||||||
FWeb.OnShouldStartLoadWithRequest := OnWebNavigate;
|
FWeb.OnShouldStartLoadWithRequest := OnWebNavigate;
|
||||||
FWeb.LoadFromStrings(PageHtml, '');
|
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;
|
end;
|
||||||
|
|
||||||
procedure TForm8.OnWebLoaded(ASender: TObject);
|
procedure TForm8.OnWebLoaded(ASender: TObject);
|
||||||
@@ -3206,33 +3226,54 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm8.PasteSyncSettings;
|
procedure TForm8.PasteSyncSettings;
|
||||||
var
|
|
||||||
LDoc: TJSONObject;
|
|
||||||
LUrl, LTok: string;
|
|
||||||
begin
|
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 := '';
|
LUrl := '';
|
||||||
LTok := '';
|
LTok := '';
|
||||||
LDoc := TJSONObject.ParseJSONValue(CurrentClipboard.Trim) as TJSONObject;
|
for LPair in Copy(ALink, Length('pmserver://sync?') + 1, MaxInt).Split(['&']) do
|
||||||
if LDoc <> nil then
|
|
||||||
try
|
|
||||||
if LDoc.GetValue<Integer>('pmsync', 0) = 1 then
|
|
||||||
begin
|
begin
|
||||||
LUrl := LDoc.GetValue<string>('u', '');
|
LEq := Pos('=', LPair);
|
||||||
LTok := LDoc.GetValue<string>('t', '');
|
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;
|
end;
|
||||||
finally
|
if (LUrl = '') or (LTok = '') then Exit;
|
||||||
LDoc.Free;
|
Result := True;
|
||||||
end;
|
Log('Sync settings received from a link: token ' + Fingerprint(LTok));
|
||||||
if (LUrl = '') or (LTok = '') then
|
WebCall('openSettings()');
|
||||||
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));
|
|
||||||
WebCall(Format('PM.pasteConfig(%s, %s)', [JsArg(LUrl), JsArg(LTok)]));
|
WebCall(Format('PM.pasteConfig(%s, %s)', [JsArg(LUrl), JsArg(LTok)]));
|
||||||
end;
|
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;
|
function TForm8.CachePwd: string;
|
||||||
begin
|
begin
|
||||||
if HasLocalContainer then Result := FVaultPwd else Result := FSyncPwd;
|
if HasLocalContainer then Result := FVaultPwd else Result := FSyncPwd;
|
||||||
|
|||||||
Reference in New Issue
Block a user