feat(licence): Settings affiche le porteur de la licence

Porteur, courriel, date d'emission et Installation ID, lus dans la charge
utile deja verifiee. Rempli une fois au demarrage sur les deux chemins
(deja licencie, activation reussie) : rien n'y change pendant une
session. Section masquee hors navigateur hote.

Les champs sont echappes avant interpolation dans ExecuteJavaScript bien
qu'ils viennent d'une charge utile signee — signee ne veut pas dire
inoffensive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-09-05 16:19:24 +01:00
parent 61ec5ff7a1
commit 8f0bbb580f
4 changed files with 77 additions and 9 deletions
+13
View File
@@ -353,6 +353,19 @@ sur une licence acceptee — pas de bouton « plus tard ».
qui essaie d'en fabriquer une. Le journal note le verdict, **jamais la cle**. qui essaie d'en fabriquer une. Le journal note le verdict, **jamais la cle**.
- Hors du navigateur hote (`Bridge.active` faux : dev en navigateur, harnais de - Hors du navigateur hote (`Bridge.active` faux : dev en navigateur, harnais de
test) il n'y a pas de licence a verifier — la porte est ouverte. test) il n'y a pas de licence a verifier — la porte est ouverte.
- **Settings → Licence** affiche porteur, courriel, date d'emission et
Installation ID. Rempli par `applyLicenceInfo(st)` **une fois au demarrage**
(les deux chemins : deja licencie, et activation reussie) plutot que branche
sur le rendu des reglages — rien n'y change pendant une session, et le panneau
est deja dans le DOM, cache. Section masquee hors navigateur hote : une suite
de tirets ferait croire a une activation perdue.
- Les champs sont **echappes avant interpolation** dans `ExecuteJavaScript`,
bien qu'ils viennent d'une charge utile signee : signee ne veut pas dire
inoffensive, c'est l'editeur qui les a tapes au moment d'emettre.
- Piege Delphi rencontre : **une routine imbriquee ne se declare pas au milieu
d'un corps de procedure** (seulement dans la section de declaration). En
glisser une casse l'inference des methodes anonymes de tout ce qui suit —
cinq `TThread.Queue` sans rapport se sont mis a ne plus compiler.
**ORDRE OBLIGATOIRE.** Tant que `LicencePublicKeyBlob` est a zero, aucune **ORDRE OBLIGATOIRE.** Tant que `LicencePublicKeyBlob` est a zero, aucune
licence n'est valide et l'ecran d'activation est **infranchissable** — l'exe licence n'est valide et l'ecran d'activation est **infranchissable** — l'exe
+18 -5
View File
@@ -1352,14 +1352,27 @@ begin
LLicOk := PM.Licence.IsLicensed; LLicOk := PM.Licence.IsLicensed;
LLicInfo := PM.Licence.LicenceInfo; LLicInfo := PM.Licence.LicenceInfo;
end; end;
var LHolder := LLicInfo.Holder; // Le nom, le courriel et la date viennent d'une charge utile QUE NOUS AVONS
if LHolder = '' then LHolder := LLicInfo.Email; // SIGNEE, mais ils sont interpoles dans du JavaScript : on les echappe
LHolder := StringReplace(LHolder, '\', '\\', [rfReplaceAll]); // comme n'importe quelle chaine etrangere. Signee ne veut pas dire
LHolder := StringReplace(LHolder, '"', '\"', [rfReplaceAll]); // inoffensive — c'est nous qui les avons tapes au moment d'emettre.
var LFields := TArray<string>.Create(
LLicInfo.Holder, LLicInfo.Email, LLicInfo.IssuedOn);
for var LI := 0 to High(LFields) do
begin
LFields[LI] := StringReplace(LFields[LI], '\', '\\', [rfReplaceAll]);
LFields[LI] := StringReplace(LFields[LI], '"', '\"', [rfReplaceAll]);
LFields[LI] := StringReplace(LFields[LI], #13, '\r', [rfReplaceAll]);
LFields[LI] := StringReplace(LFields[LI], #10, '\n', [rfReplaceAll]);
end;
WebBrowser.ExecuteJavaScript( WebBrowser.ExecuteJavaScript(
'if(window.Bridge&&Bridge.onLicenceResult)' + 'if(window.Bridge&&Bridge.onLicenceResult)' +
'Bridge.onLicenceResult(' + BoolToStr(LLicOk, True).ToLower + 'Bridge.onLicenceResult(' + BoolToStr(LLicOk, True).ToLower +
',"' + LHolder + '","' + PM.Licence.ThisMachineId + '")'); ',"' + LFields[0] +
'","' + PM.Licence.ThisMachineId +
'","' + LFields[1] +
'","' + LFields[2] + '")');
end end
// ---- Start with Windows (HKCU Run registry) -------------------------- // ---- Start with Windows (HKCU Run registry) --------------------------
+20
View File
@@ -990,6 +990,26 @@
</button> </button>
</div> </div>
</div> </div>
<div class="slideover-field" id="settingLicenceSection">
<div class="slideover-field-label">Licence</div>
<div class="setting-row">
<span>Licensed to</span>
<strong id="settingLicenceHolder">&mdash;</strong>
</div>
<div class="setting-row">
<span>Email</span>
<span id="settingLicenceEmail" style="color:var(--text-dim)">&mdash;</span>
</div>
<div class="setting-row">
<span>Issued</span>
<span id="settingLicenceIssued" style="color:var(--text-dim)">&mdash;</span>
</div>
<div class="setting-row">
<span>Installation ID</span>
<code id="settingLicenceMachine" style="font-family:ui-monospace,Consolas,monospace;user-select:all">&mdash;</code>
</div>
</div>
</div> </div>
</div> </div>
</aside> </aside>
+26 -4
View File
@@ -315,11 +315,17 @@ const Bridge = (() => {
}, 3000); }, 3000);
}); });
}, },
onLicenceResult(ok, holder, machine) { onLicenceResult(ok, holder, machine, email, issued) {
const r = licenceResolver; const r = licenceResolver;
if (!r) return; if (!r) return;
licenceResolver = null; licenceResolver = null;
r({ ok: !!ok, holder: holder || '', machine: machine || '' }); r({
ok: !!ok,
holder: holder || '',
machine: machine || '',
email: email || '',
issued: issued || '',
});
}, },
onPrefResult(key, value) { onPrefResult(key, value) {
const r = prefResolvers[key]; const r = prefResolvers[key];
@@ -6939,14 +6945,29 @@ function resetAutoLock() {
// La porte d'entrée : sans licence valable, rien d'autre ne se peint. // La porte d'entrée : sans licence valable, rien d'autre ne se peint.
// Ne rend la main QUE sur une licence acceptée — il n'y a pas de bouton // Ne rend la main QUE sur une licence acceptée — il n'y a pas de bouton
// "plus tard", c'est tout l'objet de l'écran. // "plus tard", c'est tout l'objet de l'écran.
// Les quatre lignes de Settings → Licence. Rien ici ne change pendant une
// session, donc c'est ecrit une fois au demarrage plutot que branche sur le
// rendu des reglages : le panneau existe deja dans le DOM, cache.
function applyLicenceInfo(st) {
const section = $('#settingLicenceSection');
if (!section) return;
// Hors du navigateur hote il n'y a pas de licence — montrer une section
// vide de tirets ferait croire a une activation perdue.
if (!Bridge.active) { section.style.display = 'none'; return; }
$('#settingLicenceHolder').textContent = st.holder || '—';
$('#settingLicenceEmail').textContent = st.email || '—';
$('#settingLicenceIssued').textContent = st.issued || '—';
$('#settingLicenceMachine').textContent = st.machine || '—';
}
async function ensureLicence() { async function ensureLicence() {
let st = await Bridge.licence('status'); let st = await Bridge.licence('status');
if (st.ok) return; if (st.ok) { applyLicenceInfo(st); return; }
// Un démarrage à froid lent peut manquer la fenêtre de réponse, et le // Un démarrage à froid lent peut manquer la fenêtre de réponse, et le
// silence se lit alors comme un refus : redemander une fois coûte moins // silence se lit alors comme un refus : redemander une fois coûte moins
// qu'un écran d'activation présenté à quelqu'un qui a déjà payé. // qu'un écran d'activation présenté à quelqu'un qui a déjà payé.
st = await Bridge.licence('status'); st = await Bridge.licence('status');
if (st.ok) return; if (st.ok) { applyLicenceInfo(st); return; }
const screen = $('#licenceScreen'); const screen = $('#licenceScreen');
const hint = $('#licenceHint'); const hint = $('#licenceHint');
@@ -6975,6 +6996,7 @@ async function ensureLicence() {
input.select(); input.select();
return; return;
} }
applyLicenceInfo(res);
screen.classList.add('is-hidden'); screen.classList.add('is-hidden');
resolve(); resolve();
}); });