Files
AppFlowy-Web/src/utils/subscription.ts
2026-01-20 17:25:08 +08:00

77 lines
2.5 KiB
TypeScript

/**
* Check if the current host is an official AppFlowy host by looking at the backend base URL.
* Official hosts are beta.appflowy.cloud and test.appflowy.cloud.
* Include localhost:8000 to cover the default dev backend when APPFLOWY_BASE_URL isn't updated.
* Self-hosted instances are not official hosts.
*/
import { Subscription, SubscriptionPlan } from '@/application/types';
import { getConfigValue } from '@/utils/runtime-config';
const OFFICIAL_HOSTNAMES = new Set(['beta.appflowy.cloud', 'test.appflowy.cloud', 'localhost']);
const PRO_ACCESS_PLANS = new Set([SubscriptionPlan.Pro, SubscriptionPlan.Team]);
function getBaseUrlHostname(): string | null {
const baseUrl = getConfigValue('APPFLOWY_BASE_URL', '').trim();
if (!baseUrl) return null;
try {
return new URL(baseUrl).hostname;
} catch (primaryError) {
// Allow hostnames without a protocol, e.g. "beta.appflowy.cloud"
try {
return new URL(`https://${baseUrl}`).hostname;
} catch (secondaryError) {
console.warn('Invalid APPFLOWY_BASE_URL provided:', secondaryError);
return null;
}
}
}
function isOfficialHostname(hostname: string | undefined | null): boolean {
if (!hostname) return false;
return OFFICIAL_HOSTNAMES.has(hostname);
}
function resolveHostname(): string | null {
const baseUrlHostname = getBaseUrlHostname();
if (baseUrlHostname) {
return baseUrlHostname;
}
if (typeof window === 'undefined') {
return null;
}
return window.location.hostname;
}
/**
* Check if the current environment is an official AppFlowy hosted instance.
*
* For testing self-hosted behavior, you can:
* 1. Set localStorage.setItem('__test_force_self_hosted', 'true') in Cypress
* 2. This will make isAppFlowyHosted() return false
*/
export function isAppFlowyHosted(): boolean {
// Allow tests to override and simulate self-hosted environment
if (typeof window !== 'undefined' && window.localStorage?.getItem('__test_force_self_hosted') === 'true') {
return false;
}
return isOfficialHostname(resolveHostname());
}
export function hasProAccessFromPlans(plans?: SubscriptionPlan[] | null): boolean {
if (!plans || plans.length === 0) return false;
return plans.some((plan) => PRO_ACCESS_PLANS.has(plan));
}
export function getProAccessPlanFromSubscriptions(subscriptions?: Subscription[] | null): SubscriptionPlan {
if (!subscriptions || subscriptions.length === 0) return SubscriptionPlan.Free;
return subscriptions.some((subscription) => PRO_ACCESS_PLANS.has(subscription.plan))
? SubscriptionPlan.Pro
: SubscriptionPlan.Free;
}