v1.0.11 — /manifest : opt-in multiChannel pour ne plus dédupliquer par version
Bug rapporté après v1.0.10 : client sur license channel=full avec les deux entries v1.5.4.32 dans le backoffice (Entry #1 tagged firefighter+full, Entry #2 tagged full), le launcher n'affichait toujours qu'une seule ligne. Root cause côté serveur, PAS côté client cette fois. Manifest.php faisait un « group by version + pick most specific » dans filterVersions() — comportement historique introduit pour ne pas faire crasher les vieux clients qui déduisaient par ToDictionary(v.Version). Résultat : Entry #2 était filtrée avant même d'atteindre le launcher. Ce filtrage était nécessaire à l'époque (vieux clients) mais bloque tous les fixes multi-channels client (v1.0.8-1.0.10) qui avaient rendu le client capable d'afficher plusieurs entries au même numéro. Fix : opt-in via query param sur /manifest?multiChannel=1 Server (Manifest.php) : • filterVersions() prend un `bool $multiChannel = false`. • Si true → skip group-by, retourne toutes les entries visibles. • Si false (défaut, vieux clients) → comportement historique préservé. • Query param `multiChannel` lu depuis $_GET, transmis à filterVersions. Client (ManifestService.FetchFromOvhAsync) : • Ajoute `?multiChannel=1` inconditionnellement. Un serveur ancien ignore silencieusement le param (pas de header d'échec). • Combiné avec &channel=X quand la license a un channel. Rétro-compat : • Vieux client (v1.0.9-) + serveur nouveau : n'envoie pas multiChannel=1, serveur dédupe comme avant, launcher ne crash pas. • Client nouveau (v1.0.11+) + serveur ancien : le param est ignoré, même comportement qu'avant (dédup côté serveur, une seule row visible). • Client nouveau + serveur nouveau (config voulue) : les deux entries remontent, le row-key-par-Id de v1.0.10 fait le reste. Bump : 1.0.10 → 1.0.11 (fix ciblé serveur+client). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
|
||||
#define MyAppName "PROSERVE Launcher"
|
||||
#define MyAppShortName "PS_Launcher"
|
||||
#define MyAppVersion "1.0.10"
|
||||
#define MyAppVersion "1.0.11"
|
||||
#define MyAppPublisher "ASTERION VR"
|
||||
#define MyAppURL "https://asterionvr.com"
|
||||
#define MyAppExeName "PS_Launcher.exe"
|
||||
|
||||
@@ -40,9 +40,17 @@ final class Manifest
|
||||
$clientChannel = null;
|
||||
}
|
||||
|
||||
// Multi-channel opt-in : les clients v1.0.10+ savent afficher plusieurs
|
||||
// entries au même numéro de version (channels distincts). Ils passent
|
||||
// ?multiChannel=1 pour signaler la capabilité. Les clients v1.0.9- (et
|
||||
// avant refactor row-by-Id) crasheraient sur ToDictionary(v.Version) →
|
||||
// le serveur reste en mode dédup pour eux (comportement historique).
|
||||
$multiChannel = !empty($_GET['multiChannel']);
|
||||
|
||||
$manifest['versions'] = self::filterVersions(
|
||||
$manifest['versions'] ?? [],
|
||||
$clientChannel
|
||||
$clientChannel,
|
||||
$multiChannel
|
||||
);
|
||||
|
||||
// Re-signature à la volée. La clé privée Ed25519 est dans config.php
|
||||
@@ -85,7 +93,7 @@ final class Manifest
|
||||
* @param array<int, array<string,mixed>> $versions
|
||||
* @return list<array<string,mixed>>
|
||||
*/
|
||||
private static function filterVersions(array $versions, ?string $clientChannel): array
|
||||
private static function filterVersions(array $versions, ?string $clientChannel, bool $multiChannel = false): array
|
||||
{
|
||||
// Étape 1 : filter visible par ce client
|
||||
$visible = [];
|
||||
@@ -102,7 +110,18 @@ final class Manifest
|
||||
}
|
||||
}
|
||||
|
||||
// Étape 2 : group by version, pick most specific per group
|
||||
// Client v1.0.10+ : bypass la dédup, retourne toutes les entries visibles.
|
||||
// Le client sait maintenant afficher plusieurs rows au même numéro (keyage
|
||||
// par entryId côté RebuildList) et bloque les collisions d'install côté
|
||||
// guard. Le badge channel n'apparaît que si plusieurs entries partagent
|
||||
// le numéro (v1.0.9+) → UX propre.
|
||||
if ($multiChannel) {
|
||||
return array_map(fn($item) => $item['entry'], $visible);
|
||||
}
|
||||
|
||||
// Étape 2 (clients v1.0.9-) : group by version, pick most specific per
|
||||
// group. Comportement historique pour ne pas crasher les vieux clients
|
||||
// qui font ToDictionary(v.Version) et exploseraient sur des duplicates.
|
||||
$byVersion = [];
|
||||
foreach ($visible as $item) {
|
||||
$key = (string)($item['entry']['version'] ?? '?');
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
<Product>PROSERVE Launcher</Product>
|
||||
<Copyright>© 2026 ASTERION VR — All rights reserved</Copyright>
|
||||
<RootNamespace>PSLauncher.App</RootNamespace>
|
||||
<Version>1.0.10</Version>
|
||||
<AssemblyVersion>1.0.10.0</AssemblyVersion>
|
||||
<FileVersion>1.0.10.0</FileVersion>
|
||||
<Version>1.0.11</Version>
|
||||
<AssemblyVersion>1.0.11.0</AssemblyVersion>
|
||||
<FileVersion>1.0.11.0</FileVersion>
|
||||
|
||||
<!-- Single-file self-contained publish profile (used by `dotnet publish`) -->
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
|
||||
@@ -146,9 +146,16 @@ public sealed class ManifestService : IManifestService
|
||||
// Whitelist côté client aussi pour éviter des chars exotiques qui casseraient l'URL.
|
||||
var channel = _channelProvider();
|
||||
var baseUrl = TrimSlash(_serverBaseUrlProvider()) + "/manifest";
|
||||
var url = !string.IsNullOrWhiteSpace(channel) && System.Text.RegularExpressions.Regex.IsMatch(channel!, "^[a-z0-9_-]{1,64}$")
|
||||
? $"{baseUrl}?channel={Uri.EscapeDataString(channel!)}"
|
||||
: baseUrl;
|
||||
// Query params : ?channel=X (filtrage par channel license) + &multiChannel=1
|
||||
// (opt-in au comportement v1.0.10+ : le serveur envoie plusieurs entries au
|
||||
// même numéro de version sans dédup, le client sait afficher chacune sur
|
||||
// sa row). Toujours envoyé — les serveurs anciens l'ignorent silencieusement.
|
||||
var qs = new System.Text.StringBuilder("?multiChannel=1");
|
||||
if (!string.IsNullOrWhiteSpace(channel) && System.Text.RegularExpressions.Regex.IsMatch(channel!, "^[a-z0-9_-]{1,64}$"))
|
||||
{
|
||||
qs.Append("&channel=").Append(Uri.EscapeDataString(channel!));
|
||||
}
|
||||
var url = baseUrl + qs.ToString();
|
||||
using var ovhCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
ovhCts.CancelAfter(OvhFetchTimeoutMs);
|
||||
using var req = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
Reference in New Issue
Block a user