Show Viaplay subscription type on each account after add/check.
Auto-probe on account add and always render an Abonnement row, with session-cookie fallback when overview is unavailable.
This commit is contained in:
parent
7cab13c5a8
commit
8be634f783
3 changed files with 139 additions and 31 deletions
|
|
@ -143,13 +143,22 @@ export function ViaplayPanel({ onChanged }: { onChanged?: (o: ViaplayOverview) =
|
|||
});
|
||||
const d = await r.json();
|
||||
if (!r.ok) throw new Error(d.error?.message ?? "Account toevoegen mislukt");
|
||||
const emailKey = newEmail.trim().toLowerCase();
|
||||
applySettings(d);
|
||||
setNewLabel("");
|
||||
setNewEmail("");
|
||||
setNewSession("");
|
||||
setNewAccessToken("");
|
||||
setNewProfileId("");
|
||||
setMsg("Viaplay-account toegevoegd");
|
||||
const added = (d.accounts as ViaplayAccount[] | undefined)?.find(
|
||||
(a) => a.email === emailKey
|
||||
);
|
||||
const sub = added?.subscriptionSummary;
|
||||
setMsg(
|
||||
sub
|
||||
? `Account toegevoegd · abonnement: ${sub}`
|
||||
: "Account toegevoegd — abonnement nog niet zichtbaar, druk Check"
|
||||
);
|
||||
} catch (e) {
|
||||
setErr(String((e as Error).message ?? e));
|
||||
} finally {
|
||||
|
|
@ -378,31 +387,43 @@ export function ViaplayPanel({ onChanged }: { onChanged?: (o: ViaplayOverview) =
|
|||
borderBottom: "1px solid var(--border, #333)",
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: "1 1 280px", minWidth: 0 }}>
|
||||
<div style={{ display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center" }}>
|
||||
<strong>{a.label || a.email}</strong>
|
||||
{healthBadge(a)}
|
||||
<span className="muted" style={{ fontSize: "0.85rem" }}>
|
||||
{a.email}
|
||||
{" · "}
|
||||
{a.hasSession && a.hasAccessToken ? "tokens ok" : "tokens missen"}
|
||||
{a.eventCount ? ` · ${a.eventCount} events` : ""}
|
||||
</span>
|
||||
{(a.subscriptionSummary || a.partnerName || a.healthError) && (
|
||||
<div style={{ width: "100%", fontSize: "0.85rem", marginTop: 2 }}>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 8,
|
||||
padding: "8px 10px",
|
||||
borderRadius: 6,
|
||||
background: "var(--surface-2, rgba(127,127,127,0.12))",
|
||||
fontSize: "0.9rem",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<span className="muted">Abonnement: </span>
|
||||
{a.subscriptionSummary ? (
|
||||
<span className="ok-banner" style={{ display: "inline", padding: "2px 8px" }}>
|
||||
{a.subscriptionSummary}
|
||||
</span>
|
||||
) : a.partnerName ? (
|
||||
<span className="muted">{a.partnerName}</span>
|
||||
<strong>{a.subscriptionSummary}</strong>
|
||||
) : (
|
||||
<span className="muted">nog niet opgehaald — druk Check</span>
|
||||
)}
|
||||
</div>
|
||||
{a.partnerName && a.subscriptionSummary && !a.subscriptionSummary.includes(a.partnerName) ? (
|
||||
<div className="muted" style={{ fontSize: "0.8rem", marginTop: 2 }}>
|
||||
Partner: {a.partnerName}
|
||||
</div>
|
||||
) : null}
|
||||
{a.healthError ? (
|
||||
<span className="muted" style={{ marginLeft: 8 }} title={a.healthError}>
|
||||
<div className="muted" style={{ fontSize: "0.8rem", marginTop: 4 }}>
|
||||
{a.healthError}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
|
||||
<button type="button" disabled={busy} onClick={() => void probeAccounts(a.id)}>
|
||||
|
|
|
|||
|
|
@ -115,7 +115,22 @@ export async function fetchAccountOverview(
|
|||
lastStatus = res.status;
|
||||
const body = asRecord(res.json);
|
||||
if (res.status === 200 && body) {
|
||||
return parseOverviewBody(body);
|
||||
const parsed = parseOverviewBody(body);
|
||||
if (!parsed.productNames.length || !parsed.partnerName) {
|
||||
const fromSession = subscriptionFromSession(creds.session);
|
||||
const partnerName = parsed.partnerName || fromSession.partnerName;
|
||||
const productNames = parsed.productNames.length
|
||||
? parsed.productNames
|
||||
: fromSession.productNames;
|
||||
const parts = [partnerName, productNames.join(", ")].filter(Boolean);
|
||||
return {
|
||||
...parsed,
|
||||
partnerName,
|
||||
productNames,
|
||||
summary: parts.join(" · ") || parsed.summary,
|
||||
};
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
lastError =
|
||||
(typeof body?.message === "string" && body.message) ||
|
||||
|
|
@ -141,21 +156,37 @@ export async function fetchAccountOverview(
|
|||
asRecord(root?._links)?.["viaplay:logout"]
|
||||
);
|
||||
if (ping.status === 200 && hasUser) {
|
||||
const fromSession = subscriptionFromSession(creds.session);
|
||||
const exp = accessTokenExpiry(creds.accessToken);
|
||||
const summary =
|
||||
fromSession.summary ||
|
||||
(exp
|
||||
? `Sessie actief · token tot ${exp}`
|
||||
: "Sessie actief (abonnementdetails niet beschikbaar)");
|
||||
return {
|
||||
ok: true,
|
||||
status: ping.status,
|
||||
partnerName: null,
|
||||
productNames: [],
|
||||
summary: exp
|
||||
? `Sessie actief · token tot ${exp}`
|
||||
: "Sessie actief (abonnementdetails niet beschikbaar)",
|
||||
partnerName: fromSession.partnerName,
|
||||
productNames: fromSession.productNames,
|
||||
summary,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
|
||||
// Laatste redmiddel: abonnement uit session-cookie (lokaal, geen extra call)
|
||||
const fromSession = subscriptionFromSession(creds.session);
|
||||
if (fromSession.summary) {
|
||||
return {
|
||||
ok: true,
|
||||
status: lastStatus || 200,
|
||||
partnerName: fromSession.partnerName,
|
||||
productNames: fromSession.productNames,
|
||||
summary: fromSession.summary,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
status: lastStatus,
|
||||
|
|
@ -166,6 +197,52 @@ export async function fetchAccountOverview(
|
|||
};
|
||||
}
|
||||
|
||||
/** Haal partner/pakket uit de Viaplay session-cookie (zonder netwerk). */
|
||||
export function subscriptionFromSession(session: string | null | undefined): {
|
||||
partnerName: string | null;
|
||||
productNames: string[];
|
||||
summary: string | null;
|
||||
} {
|
||||
if (!session?.trim()) {
|
||||
return { partnerName: null, productNames: [], summary: null };
|
||||
}
|
||||
try {
|
||||
let raw = session.trim();
|
||||
try {
|
||||
raw = decodeURIComponent(raw);
|
||||
} catch {
|
||||
/* already decoded */
|
||||
}
|
||||
// express-session vorm: s:j:{json}.{signature}
|
||||
const m = /^s:j:(.+)\.[^.]+$/s.exec(raw);
|
||||
const jsonText = m?.[1] ?? null;
|
||||
if (!jsonText) return { partnerName: null, productNames: [], summary: null };
|
||||
const data = JSON.parse(jsonText) as Record<string, unknown>;
|
||||
const contracts = String(data.vmsContracts ?? "").trim();
|
||||
const productNames: string[] = [];
|
||||
let partnerName: string | null = null;
|
||||
if (/tmobile|odido/i.test(contracts) || data.isVmsCustomer === true) {
|
||||
partnerName = "Odido Internet + TV";
|
||||
}
|
||||
if (/b2b_vtotal|vtotal/i.test(contracts)) productNames.push("Total / V Total");
|
||||
else if (/basis|basic/i.test(contracts)) productNames.push("Basis");
|
||||
else if (contracts) {
|
||||
productNames.push(contracts.replace(/\s+/g, " "));
|
||||
}
|
||||
if (data.isTveBCustomer === true && !productNames.length) {
|
||||
productNames.push("TV-pakket");
|
||||
}
|
||||
const parts = [partnerName, productNames.join(", ")].filter(Boolean);
|
||||
return {
|
||||
partnerName,
|
||||
productNames,
|
||||
summary: parts.length ? parts.join(" · ") : null,
|
||||
};
|
||||
} catch {
|
||||
return { partnerName: null, productNames: [], summary: null };
|
||||
}
|
||||
}
|
||||
|
||||
function accessTokenExpiry(token: string | null): string | null {
|
||||
if (!token) return null;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -216,7 +216,8 @@ export async function addViaplayAccount(
|
|||
}
|
||||
config.accounts.push(account);
|
||||
await persist(config, existing?.enabled ?? true);
|
||||
return getViaplaySettingsPublic();
|
||||
// Direct abonnement ophalen zodat UI het meteen toont
|
||||
return probeViaplayAccounts(sessionSecret, account.id);
|
||||
}
|
||||
|
||||
export async function updateViaplayAccount(
|
||||
|
|
@ -286,6 +287,15 @@ export async function updateViaplayAccount(
|
|||
: current.passwordEnc,
|
||||
};
|
||||
await persist(config, existing?.enabled ?? true);
|
||||
const tokensChanged = !!(
|
||||
input.session?.trim() ||
|
||||
input.accessToken?.trim() ||
|
||||
input.profileId !== undefined ||
|
||||
input.clearCookies
|
||||
);
|
||||
if (tokensChanged) {
|
||||
return probeViaplayAccounts(sessionSecret, accountId);
|
||||
}
|
||||
return getViaplaySettingsPublic();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue