Admin stores email/password; tray uses Playwright against real Chrome to click login and push tokens.
150 lines
4.1 KiB
TypeScript
150 lines
4.1 KiB
TypeScript
import { Notification } from "electron";
|
|
import type { TrayConfig } from "./config";
|
|
import { saveConfig } from "./config";
|
|
import { loginViaplayUi } from "./login/viaplay";
|
|
import { loginF1tvUi } from "./login/f1tv";
|
|
|
|
export type AgentAccount = {
|
|
provider: "viaplay" | "f1tv";
|
|
id: string;
|
|
email: string;
|
|
label: string;
|
|
password: string | null;
|
|
hasTokens: boolean;
|
|
enabled: boolean;
|
|
};
|
|
|
|
export async function fetchAgentAccounts(cfg: TrayConfig): Promise<AgentAccount[]> {
|
|
const res = await fetch(`${cfg.apiUrl}/api/v1/agent/accounts`, {
|
|
headers: { "X-Tray-Key": cfg.trayKey },
|
|
});
|
|
const data = (await res.json().catch(() => ({}))) as {
|
|
error?: { message?: string };
|
|
accounts?: AgentAccount[];
|
|
};
|
|
if (!res.ok) throw new Error(data.error?.message || `HTTP ${res.status}`);
|
|
return data.accounts ?? [];
|
|
}
|
|
|
|
async function pushViaplay(
|
|
cfg: TrayConfig,
|
|
email: string,
|
|
label: string,
|
|
tokens: {
|
|
session: string;
|
|
accessToken: string;
|
|
profileId: string | null;
|
|
persistentLogin: string | null;
|
|
}
|
|
) {
|
|
const res = await fetch(`${cfg.apiUrl}/api/v1/agent/viaplay/tokens`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-Tray-Key": cfg.trayKey,
|
|
},
|
|
body: JSON.stringify({
|
|
email,
|
|
label,
|
|
session: tokens.session,
|
|
accessToken: tokens.accessToken,
|
|
profileId: tokens.profileId || undefined,
|
|
persistentLogin: tokens.persistentLogin || undefined,
|
|
}),
|
|
});
|
|
const data = (await res.json().catch(() => ({}))) as { error?: { message?: string } };
|
|
if (!res.ok) throw new Error(data.error?.message || `HTTP ${res.status}`);
|
|
}
|
|
|
|
async function pushF1tv(
|
|
cfg: TrayConfig,
|
|
email: string,
|
|
label: string,
|
|
tokens: {
|
|
ascendontoken: string;
|
|
entitlementtoken: string;
|
|
sessionId: string | null;
|
|
}
|
|
) {
|
|
const res = await fetch(`${cfg.apiUrl}/api/v1/agent/f1tv/tokens`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-Tray-Key": cfg.trayKey,
|
|
},
|
|
body: JSON.stringify({
|
|
email,
|
|
label,
|
|
ascendontoken: tokens.ascendontoken,
|
|
entitlementtoken: tokens.entitlementtoken,
|
|
sessionId: tokens.sessionId || undefined,
|
|
}),
|
|
});
|
|
const data = (await res.json().catch(() => ({}))) as { error?: { message?: string } };
|
|
if (!res.ok) throw new Error(data.error?.message || `HTTP ${res.status}`);
|
|
}
|
|
|
|
export type RefreshSummary = {
|
|
ok: number;
|
|
failed: number;
|
|
skipped: number;
|
|
errors: string[];
|
|
};
|
|
|
|
/** Headless UI-login voor alle accounts met wachtwoord. */
|
|
export async function refreshAllSilent(
|
|
cfg: TrayConfig,
|
|
opts?: { headless?: boolean; providers?: Array<"viaplay" | "f1tv"> }
|
|
): Promise<RefreshSummary> {
|
|
const headless = opts?.headless !== false;
|
|
const providers = new Set(opts?.providers ?? ["viaplay", "f1tv"]);
|
|
const accounts = await fetchAgentAccounts(cfg);
|
|
const summary: RefreshSummary = { ok: 0, failed: 0, skipped: 0, errors: [] };
|
|
|
|
for (const acc of accounts) {
|
|
if (!providers.has(acc.provider)) continue;
|
|
if (!acc.password?.trim()) {
|
|
summary.skipped += 1;
|
|
continue;
|
|
}
|
|
try {
|
|
if (acc.provider === "viaplay") {
|
|
const tokens = await loginViaplayUi({
|
|
accountId: acc.id,
|
|
email: acc.email,
|
|
password: acc.password,
|
|
headless,
|
|
});
|
|
await pushViaplay(cfg, acc.email, acc.label, tokens);
|
|
} else {
|
|
const tokens = await loginF1tvUi({
|
|
accountId: acc.id,
|
|
email: acc.email,
|
|
password: acc.password,
|
|
headless,
|
|
});
|
|
await pushF1tv(cfg, acc.email, acc.label, tokens);
|
|
}
|
|
summary.ok += 1;
|
|
} catch (err) {
|
|
summary.failed += 1;
|
|
summary.errors.push(
|
|
`${acc.provider}:${acc.email}: ${err instanceof Error ? err.message : String(err)}`
|
|
);
|
|
}
|
|
}
|
|
|
|
cfg.lastPushAt = new Date().toISOString();
|
|
cfg.lastPushSummary = `Stil: ${summary.ok} ok · ${summary.failed} fout · ${summary.skipped} overgeslagen`;
|
|
cfg.lastError = summary.errors[0];
|
|
saveConfig(cfg);
|
|
|
|
if (Notification.isSupported()) {
|
|
new Notification({
|
|
title: "Token Tray",
|
|
body: cfg.lastPushSummary,
|
|
}).show();
|
|
}
|
|
|
|
return summary;
|
|
}
|