mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-08 20:26:08 +02:00
151 lines
4.8 KiB
TypeScript
151 lines
4.8 KiB
TypeScript
import type { CloudUser, Entitlements } from "@/types";
|
|
|
|
const DEFAULT_REQUESTS_PER_HOUR = 100;
|
|
|
|
interface Capabilities {
|
|
browserAutomation: boolean;
|
|
crossOsFingerprints: boolean;
|
|
cloudBackup: boolean;
|
|
teamCollaboration: boolean;
|
|
cookieBot: boolean;
|
|
remoteInteractive: boolean;
|
|
}
|
|
|
|
const NONE: Entitlements = {
|
|
active: false,
|
|
browserAutomation: false,
|
|
crossOsFingerprints: false,
|
|
cloudBackup: false,
|
|
teamCollaboration: false,
|
|
cookieBot: false,
|
|
remoteInteractive: false,
|
|
profileLimit: 0,
|
|
requestsPerHour: 0,
|
|
remoteBrowserHours: 0,
|
|
};
|
|
|
|
// Mirror of PLAN_CAPABILITIES in apps/backend/src/plans/entitlements.ts. Keep in
|
|
// sync — a new plan must be declared here too, or it falls back to DEFAULT_PAID.
|
|
const PLAN_CAPABILITIES: Record<string, Capabilities> = {
|
|
// The one row where cookieBot, browserAutomation and remoteInteractive all
|
|
// disagree: solo pays for a nightly bot and nothing else that drives a
|
|
// browser. No fingerprint editing either.
|
|
solo: {
|
|
browserAutomation: false,
|
|
crossOsFingerprints: false,
|
|
cloudBackup: true,
|
|
teamCollaboration: false,
|
|
cookieBot: true,
|
|
remoteInteractive: false,
|
|
},
|
|
pro: {
|
|
browserAutomation: true,
|
|
crossOsFingerprints: true,
|
|
cloudBackup: true,
|
|
teamCollaboration: false,
|
|
cookieBot: true,
|
|
remoteInteractive: true,
|
|
},
|
|
team: {
|
|
browserAutomation: true,
|
|
crossOsFingerprints: true,
|
|
cloudBackup: true,
|
|
teamCollaboration: true,
|
|
cookieBot: true,
|
|
remoteInteractive: true,
|
|
},
|
|
enterprise: {
|
|
browserAutomation: true,
|
|
crossOsFingerprints: true,
|
|
cloudBackup: true,
|
|
teamCollaboration: true,
|
|
cookieBot: true,
|
|
remoteInteractive: true,
|
|
},
|
|
};
|
|
|
|
// Unknown paid plan -> pro-level (never team), matching the backend default.
|
|
const DEFAULT_PAID: Capabilities = {
|
|
browserAutomation: true,
|
|
crossOsFingerprints: true,
|
|
cloudBackup: true,
|
|
teamCollaboration: false,
|
|
cookieBot: true,
|
|
remoteInteractive: true,
|
|
};
|
|
|
|
/**
|
|
* The user's effective entitlements. Prefers the backend-resolved object the
|
|
* desktop attaches to CloudUser; only falls back to deriving from the plan
|
|
* fields when it's missing (older cached state). The fallback mirrors the
|
|
* backend matrix in `apps/backend/src/plans/entitlements.ts`.
|
|
*/
|
|
export function getEntitlements(
|
|
user: CloudUser | null | undefined,
|
|
): Entitlements {
|
|
if (user?.entitlements) {
|
|
const server = user.entitlements;
|
|
// A backend (or a cached login) older than the current release omits these
|
|
// keys. Reading them as `undefined` would hide a paid feature from a paying
|
|
// customer with nothing logged anywhere, so resolve them here — the one
|
|
// place every caller already goes through.
|
|
//
|
|
// Both absent flags fall back to `browserAutomation`, which is what they
|
|
// were derived from before solo existed: on every plan a pre-solo backend
|
|
// knows about, automation implied both the bot and interactive remote
|
|
// control. A solo user never hits this branch — the backend that can put
|
|
// them on solo is by definition new enough to send both keys.
|
|
//
|
|
// `remoteBrowserHours` stays 0 because the spendable figure is whatever
|
|
// `get_remote_hours_quota` reports, never a client guess.
|
|
return {
|
|
...server,
|
|
cookieBot: server.cookieBot ?? server.browserAutomation,
|
|
remoteInteractive: server.remoteInteractive ?? server.browserAutomation,
|
|
remoteBrowserHours: server.remoteBrowserHours ?? 0,
|
|
};
|
|
}
|
|
if (!user) return NONE;
|
|
|
|
const active =
|
|
user.plan !== "free" &&
|
|
(user.subscriptionStatus === "active" || user.planPeriod === "lifetime");
|
|
if (!active) return NONE;
|
|
|
|
const caps = PLAN_CAPABILITIES[user.plan] ?? DEFAULT_PAID;
|
|
return {
|
|
active: true,
|
|
browserAutomation: caps.browserAutomation,
|
|
crossOsFingerprints: caps.crossOsFingerprints,
|
|
cloudBackup: caps.cloudBackup,
|
|
teamCollaboration: caps.teamCollaboration,
|
|
cookieBot: caps.cookieBot,
|
|
remoteInteractive: caps.remoteInteractive,
|
|
profileLimit: user.profileLimit,
|
|
requestsPerHour: caps.browserAutomation ? DEFAULT_REQUESTS_PER_HOUR : 0,
|
|
remoteBrowserHours: 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Whether this user may enrol profiles in Cookie Bot. Every gate in the UI
|
|
* goes through here so a plan change is one edit, and so the Pro badge and the
|
|
* control it guards can never disagree.
|
|
*/
|
|
export function canUseCookieBot(user: CloudUser | null | undefined): boolean {
|
|
const entitlements = getEntitlements(user);
|
|
return entitlements.active && entitlements.cookieBot;
|
|
}
|
|
|
|
/**
|
|
* Only a team owner sees per-member attribution. An admin can change team
|
|
* settings but the pooled spend is the owner's bill.
|
|
*/
|
|
export function isTeamOwner(user: CloudUser | null | undefined): boolean {
|
|
return (
|
|
getEntitlements(user).teamCollaboration &&
|
|
user?.teamRole === "owner" &&
|
|
Boolean(user.teamId)
|
|
);
|
|
}
|