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();
|
const d = await r.json();
|
||||||
if (!r.ok) throw new Error(d.error?.message ?? "Account toevoegen mislukt");
|
if (!r.ok) throw new Error(d.error?.message ?? "Account toevoegen mislukt");
|
||||||
|
const emailKey = newEmail.trim().toLowerCase();
|
||||||
applySettings(d);
|
applySettings(d);
|
||||||
setNewLabel("");
|
setNewLabel("");
|
||||||
setNewEmail("");
|
setNewEmail("");
|
||||||
setNewSession("");
|
setNewSession("");
|
||||||
setNewAccessToken("");
|
setNewAccessToken("");
|
||||||
setNewProfileId("");
|
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) {
|
} catch (e) {
|
||||||
setErr(String((e as Error).message ?? e));
|
setErr(String((e as Error).message ?? e));
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -378,31 +387,43 @@ export function ViaplayPanel({ onChanged }: { onChanged?: (o: ViaplayOverview) =
|
||||||
borderBottom: "1px solid var(--border, #333)",
|
borderBottom: "1px solid var(--border, #333)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div style={{ flex: "1 1 280px", minWidth: 0 }}>
|
||||||
<div style={{ display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center" }}>
|
<div style={{ display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center" }}>
|
||||||
<strong>{a.label || a.email}</strong>
|
<strong>{a.label || a.email}</strong>
|
||||||
{healthBadge(a)}
|
{healthBadge(a)}
|
||||||
<span className="muted" style={{ fontSize: "0.85rem" }}>
|
<span className="muted" style={{ fontSize: "0.85rem" }}>
|
||||||
{a.email}
|
{a.email}
|
||||||
{" · "}
|
|
||||||
{a.hasSession && a.hasAccessToken ? "tokens ok" : "tokens missen"}
|
|
||||||
{a.eventCount ? ` · ${a.eventCount} events` : ""}
|
{a.eventCount ? ` · ${a.eventCount} events` : ""}
|
||||||
</span>
|
</span>
|
||||||
{(a.subscriptionSummary || a.partnerName || a.healthError) && (
|
</div>
|
||||||
<div style={{ width: "100%", fontSize: "0.85rem", marginTop: 2 }}>
|
<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 ? (
|
{a.subscriptionSummary ? (
|
||||||
<span className="ok-banner" style={{ display: "inline", padding: "2px 8px" }}>
|
<strong>{a.subscriptionSummary}</strong>
|
||||||
{a.subscriptionSummary}
|
) : (
|
||||||
</span>
|
<span className="muted">nog niet opgehaald — druk Check</span>
|
||||||
) : a.partnerName ? (
|
)}
|
||||||
<span className="muted">{a.partnerName}</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}
|
) : null}
|
||||||
{a.healthError ? (
|
{a.healthError ? (
|
||||||
<span className="muted" style={{ marginLeft: 8 }} title={a.healthError}>
|
<div className="muted" style={{ fontSize: "0.8rem", marginTop: 4 }}>
|
||||||
{a.healthError}
|
{a.healthError}
|
||||||
</span>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
|
<div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
|
||||||
<button type="button" disabled={busy} onClick={() => void probeAccounts(a.id)}>
|
<button type="button" disabled={busy} onClick={() => void probeAccounts(a.id)}>
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,22 @@ export async function fetchAccountOverview(
|
||||||
lastStatus = res.status;
|
lastStatus = res.status;
|
||||||
const body = asRecord(res.json);
|
const body = asRecord(res.json);
|
||||||
if (res.status === 200 && body) {
|
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 =
|
lastError =
|
||||||
(typeof body?.message === "string" && body.message) ||
|
(typeof body?.message === "string" && body.message) ||
|
||||||
|
|
@ -141,21 +156,37 @@ export async function fetchAccountOverview(
|
||||||
asRecord(root?._links)?.["viaplay:logout"]
|
asRecord(root?._links)?.["viaplay:logout"]
|
||||||
);
|
);
|
||||||
if (ping.status === 200 && hasUser) {
|
if (ping.status === 200 && hasUser) {
|
||||||
|
const fromSession = subscriptionFromSession(creds.session);
|
||||||
const exp = accessTokenExpiry(creds.accessToken);
|
const exp = accessTokenExpiry(creds.accessToken);
|
||||||
|
const summary =
|
||||||
|
fromSession.summary ||
|
||||||
|
(exp
|
||||||
|
? `Sessie actief · token tot ${exp}`
|
||||||
|
: "Sessie actief (abonnementdetails niet beschikbaar)");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
status: ping.status,
|
status: ping.status,
|
||||||
partnerName: null,
|
partnerName: fromSession.partnerName,
|
||||||
productNames: [],
|
productNames: fromSession.productNames,
|
||||||
summary: exp
|
summary,
|
||||||
? `Sessie actief · token tot ${exp}`
|
|
||||||
: "Sessie actief (abonnementdetails niet beschikbaar)",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore */
|
/* 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 {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
status: lastStatus,
|
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 {
|
function accessTokenExpiry(token: string | null): string | null {
|
||||||
if (!token) return null;
|
if (!token) return null;
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,8 @@ export async function addViaplayAccount(
|
||||||
}
|
}
|
||||||
config.accounts.push(account);
|
config.accounts.push(account);
|
||||||
await persist(config, existing?.enabled ?? true);
|
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(
|
export async function updateViaplayAccount(
|
||||||
|
|
@ -286,6 +287,15 @@ export async function updateViaplayAccount(
|
||||||
: current.passwordEnc,
|
: current.passwordEnc,
|
||||||
};
|
};
|
||||||
await persist(config, existing?.enabled ?? true);
|
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();
|
return getViaplaySettingsPublic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue