mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
630 lines
18 KiB
TypeScript
630 lines
18 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
import {
|
|
DEFAULT_GIF_PROVIDER_INFO,
|
|
type GifProvider,
|
|
type GifProviderInfo,
|
|
type GifProviderInfoInput,
|
|
normalizeGifProviderInfo,
|
|
} from '@app/features/app/state/GifProviderConfig';
|
|
import DeveloperOptions from '@app/features/devtools/state/DeveloperOptions';
|
|
import {http} from '@app/features/platform/transport/RestTransport';
|
|
import {API_CODE_VERSION} from '@fluxer/constants/src/AppConstants';
|
|
import type {
|
|
InstanceAppPublic,
|
|
InstanceCaptcha,
|
|
InstanceCommunity,
|
|
InstanceDiscoveryResponse,
|
|
InstanceFeatures,
|
|
InstanceRegistration,
|
|
InstanceServices,
|
|
InstanceSso as InstanceSsoConfig,
|
|
} from '@fluxer/instance_bootstrap/src/Types';
|
|
import {expandWireFormat} from '@fluxer/limits/src/LimitDiffer';
|
|
import type {LimitConfigSnapshot, LimitConfigWireFormat} from '@fluxer/limits/src/LimitTypes';
|
|
import type {InstanceConfigResponse} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
|
import {makeAutoObservable, reaction, runInAction} from 'mobx';
|
|
|
|
export type {
|
|
InstanceCaptcha,
|
|
InstanceCommunity,
|
|
InstanceDiscoveryResponse,
|
|
InstanceFeatures,
|
|
InstanceRegistration,
|
|
InstanceServices,
|
|
InstanceSsoConfig,
|
|
};
|
|
export type {GifProvider, GifProviderInfo};
|
|
|
|
export interface RuntimeConfigSnapshot {
|
|
apiEndpoint: string;
|
|
apiPublicEndpoint: string;
|
|
gatewayEndpoint: string;
|
|
mediaEndpoint: string;
|
|
staticCdnEndpoint: string;
|
|
marketingEndpoint: string;
|
|
adminEndpoint: string;
|
|
inviteEndpoint: string;
|
|
giftEndpoint: string;
|
|
webAppEndpoint: string;
|
|
gifProvider: GifProvider;
|
|
gifProviderDisplayName: string;
|
|
gifAttributionRequired: boolean;
|
|
captchaProvider: 'hcaptcha' | 'turnstile' | 'none';
|
|
hcaptchaSiteKey: string | null;
|
|
turnstileSiteKey: string | null;
|
|
apiCodeVersion: number;
|
|
features: InstanceFeatures;
|
|
sso: InstanceSsoConfig | null;
|
|
registration?: InstanceRegistration;
|
|
community?: InstanceCommunity;
|
|
services?: InstanceServices;
|
|
publicPushVapidKey: string | null;
|
|
limits: LimitConfigSnapshot;
|
|
appPublic: InstanceAppPublic;
|
|
}
|
|
|
|
function runtimeInstanceKey(snapshot: RuntimeConfigSnapshot): string | null {
|
|
try {
|
|
const endpoint = snapshot.apiEndpoint.trim();
|
|
const url = new URL(endpoint);
|
|
if (
|
|
(url.protocol !== 'https:' && url.protocol !== 'http:') ||
|
|
url.username ||
|
|
url.password ||
|
|
url.search ||
|
|
url.hash
|
|
) {
|
|
return null;
|
|
}
|
|
const path = url.pathname.replace(/\/+$/u, '');
|
|
return `${url.origin.toLowerCase()}${path}`;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function runtimeConfigSnapshotsAreSameInstance(
|
|
left: RuntimeConfigSnapshot | undefined,
|
|
right: RuntimeConfigSnapshot,
|
|
): boolean {
|
|
if (!left) {
|
|
return false;
|
|
}
|
|
const leftKey = runtimeInstanceKey(left);
|
|
const rightKey = runtimeInstanceKey(right);
|
|
return leftKey !== null && leftKey === rightKey;
|
|
}
|
|
|
|
const DEFAULT_INSTANCE_FEATURES: InstanceFeatures = {
|
|
voice_enabled: false,
|
|
stripe_enabled: false,
|
|
self_hosted: false,
|
|
presigned_attachment_uploads: false,
|
|
emails_enabled: false,
|
|
};
|
|
|
|
export const DEFAULT_INSTANCE_REGISTRATION: InstanceRegistration = {
|
|
mode: 'open',
|
|
admin_registration_urls_enabled: true,
|
|
};
|
|
|
|
export const DEFAULT_INSTANCE_COMMUNITY: InstanceCommunity = {
|
|
single_community: false,
|
|
single_community_guild_id: null,
|
|
direct_messages_disabled: false,
|
|
};
|
|
|
|
export function normalizeInstanceCommunity(community?: InstanceCommunity | null): InstanceCommunity {
|
|
return {
|
|
...DEFAULT_INSTANCE_COMMUNITY,
|
|
...(community ?? {}),
|
|
};
|
|
}
|
|
|
|
export const DEFAULT_INSTANCE_SERVICES: InstanceServices = {
|
|
gif_enabled: true,
|
|
youtube_enabled: false,
|
|
bluesky_enabled: false,
|
|
};
|
|
|
|
export function normalizeInstanceServices(services?: InstanceServices | null): InstanceServices {
|
|
return {
|
|
...DEFAULT_INSTANCE_SERVICES,
|
|
...(services ?? {}),
|
|
};
|
|
}
|
|
|
|
export const DEFAULT_APP_PUBLIC_CONFIG: InstanceAppPublic = {
|
|
branding: {
|
|
product_name: 'Fluxer',
|
|
icon_url: null,
|
|
symbol_url: null,
|
|
logo_url: null,
|
|
wordmark_url: null,
|
|
favicon_url: null,
|
|
theme_color: null,
|
|
},
|
|
setup: {
|
|
configured: false,
|
|
admin_url: null,
|
|
},
|
|
legal: {
|
|
terms_url: null,
|
|
privacy_url: null,
|
|
},
|
|
registration: {
|
|
collect_date_of_birth: true,
|
|
},
|
|
};
|
|
|
|
export function normalizeInstanceRegistration(registration?: InstanceRegistration | null): InstanceRegistration {
|
|
return {
|
|
...DEFAULT_INSTANCE_REGISTRATION,
|
|
...(registration ?? {}),
|
|
};
|
|
}
|
|
|
|
function getInlinedInstance(): InstanceDiscoveryResponse {
|
|
const bootstrap = typeof window !== 'undefined' ? window.__FLUXER_BOOTSTRAP__ : undefined;
|
|
if (!bootstrap) {
|
|
throw new Error('window.__FLUXER_BOOTSTRAP__ is missing — app must be served by fluxer_app_proxy');
|
|
}
|
|
return bootstrap.instance;
|
|
}
|
|
|
|
let lastAppliedDocumentProductName = DEFAULT_APP_PUBLIC_CONFIG.branding.product_name;
|
|
|
|
function escapeRegExp(value: string): string {
|
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
}
|
|
|
|
function upsertDocumentLink(rel: string, href: string): void {
|
|
if (typeof document === 'undefined') return;
|
|
const selector = `link[rel="${rel}"][data-fluxer-branding="true"]`;
|
|
const existing = document.head.querySelector<HTMLLinkElement>(selector);
|
|
const link = existing ?? document.createElement('link');
|
|
link.rel = rel;
|
|
link.href = href;
|
|
link.dataset.fluxerBranding = 'true';
|
|
if (!existing) {
|
|
document.head.appendChild(link);
|
|
}
|
|
}
|
|
|
|
function removeDocumentLink(rel: string): void {
|
|
if (typeof document === 'undefined') return;
|
|
document.head.querySelectorAll<HTMLLinkElement>(`link[rel="${rel}"][data-fluxer-branding="true"]`).forEach((link) => {
|
|
link.remove();
|
|
});
|
|
}
|
|
|
|
function upsertDocumentMeta(name: string, content: string): void {
|
|
if (typeof document === 'undefined') return;
|
|
const brandedMeta = document.head.querySelector<HTMLMetaElement>(`meta[name="${name}"][data-fluxer-branding="true"]`);
|
|
const meta = brandedMeta ?? document.createElement('meta');
|
|
meta.name = name;
|
|
meta.content = content;
|
|
meta.dataset.fluxerBranding = 'true';
|
|
if (brandedMeta) return;
|
|
const precedingMeta = document.head.querySelector<HTMLMetaElement>(`meta[name="${name}"]`);
|
|
document.head.insertBefore(meta, precedingMeta);
|
|
}
|
|
|
|
function removeDocumentMeta(name: string): void {
|
|
if (typeof document === 'undefined') return;
|
|
document.head
|
|
.querySelectorAll<HTMLMetaElement>(`meta[name="${name}"][data-fluxer-branding="true"]`)
|
|
.forEach((meta) => {
|
|
meta.remove();
|
|
});
|
|
}
|
|
|
|
function applyDocumentTitleProductName(previousProductName: string, nextProductName: string): void {
|
|
if (typeof document === 'undefined' || previousProductName === nextProductName) return;
|
|
const productPrefix = new RegExp(
|
|
`^(\\(\\d+\\)\\s+|\\u2022\\s+)?${escapeRegExp(previousProductName)}(?=$| \\| )`,
|
|
'u',
|
|
);
|
|
if (productPrefix.test(document.title)) {
|
|
document.title = document.title.replace(productPrefix, (_match, prefix: string | undefined) => {
|
|
return `${prefix ?? ''}${nextProductName}`;
|
|
});
|
|
}
|
|
}
|
|
|
|
function applyDocumentBranding(appPublic: InstanceAppPublic): void {
|
|
if (typeof document === 'undefined') return;
|
|
const productName = appPublic.branding.product_name.trim() || DEFAULT_APP_PUBLIC_CONFIG.branding.product_name;
|
|
applyDocumentTitleProductName(lastAppliedDocumentProductName, productName);
|
|
lastAppliedDocumentProductName = productName;
|
|
upsertDocumentMeta('application-name', productName);
|
|
upsertDocumentMeta('apple-mobile-web-app-title', productName);
|
|
const faviconUrl = appPublic.branding.favicon_url ?? appPublic.branding.icon_url;
|
|
if (faviconUrl) {
|
|
upsertDocumentLink('icon', faviconUrl);
|
|
} else {
|
|
removeDocumentLink('icon');
|
|
}
|
|
if (appPublic.branding.theme_color) {
|
|
upsertDocumentMeta('theme-color', appPublic.branding.theme_color);
|
|
} else {
|
|
removeDocumentMeta('theme-color');
|
|
}
|
|
}
|
|
|
|
export function normalizeAppPublicConfig(appPublic?: Partial<InstanceAppPublic> | null): InstanceAppPublic {
|
|
return {
|
|
branding: {
|
|
...DEFAULT_APP_PUBLIC_CONFIG.branding,
|
|
...(appPublic?.branding ?? {}),
|
|
},
|
|
setup: {
|
|
...DEFAULT_APP_PUBLIC_CONFIG.setup,
|
|
...(appPublic?.setup ?? {}),
|
|
},
|
|
legal: {
|
|
...DEFAULT_APP_PUBLIC_CONFIG.legal,
|
|
...(appPublic?.legal ?? {}),
|
|
},
|
|
registration: {
|
|
...DEFAULT_APP_PUBLIC_CONFIG.registration,
|
|
...(appPublic?.registration ?? {}),
|
|
},
|
|
};
|
|
}
|
|
|
|
class RuntimeConfig {
|
|
apiEndpoint: string = '';
|
|
apiPublicEndpoint: string = '';
|
|
gatewayEndpoint: string = '';
|
|
mediaEndpoint: string = '';
|
|
staticCdnEndpoint: string = '';
|
|
marketingEndpoint: string = '';
|
|
adminEndpoint: string = '';
|
|
inviteEndpoint: string = '';
|
|
giftEndpoint: string = '';
|
|
webAppEndpoint: string = '';
|
|
gifProvider: GifProvider = DEFAULT_GIF_PROVIDER_INFO.name;
|
|
gifProviderDisplayName: string = DEFAULT_GIF_PROVIDER_INFO.displayName;
|
|
gifAttributionRequired: boolean = DEFAULT_GIF_PROVIDER_INFO.attributionRequired;
|
|
captchaProvider: 'hcaptcha' | 'turnstile' | 'none' = 'none';
|
|
hcaptchaSiteKey: string | null = null;
|
|
turnstileSiteKey: string | null = null;
|
|
apiCodeVersion: number = API_CODE_VERSION;
|
|
features: InstanceFeatures = {...DEFAULT_INSTANCE_FEATURES};
|
|
sso: InstanceSsoConfig | null = null;
|
|
registration: InstanceRegistration = {...DEFAULT_INSTANCE_REGISTRATION};
|
|
community: InstanceCommunity = {...DEFAULT_INSTANCE_COMMUNITY};
|
|
services: InstanceServices = {...DEFAULT_INSTANCE_SERVICES};
|
|
publicPushVapidKey: string | null = null;
|
|
limits: LimitConfigSnapshot = this.createEmptyLimitConfig();
|
|
currentDefaultsHash: string | null = null;
|
|
appPublic: InstanceAppPublic = normalizeAppPublicConfig();
|
|
|
|
constructor() {
|
|
this.updateFromInstance(getInlinedInstance());
|
|
makeAutoObservable(this, {}, {autoBind: true});
|
|
reaction(
|
|
() => this.apiEndpoint,
|
|
(endpoint) => {
|
|
if (endpoint) {
|
|
http.configure({baseUrl: endpoint, apiVersion: this.apiCodeVersion});
|
|
}
|
|
},
|
|
{fireImmediately: true},
|
|
);
|
|
}
|
|
|
|
waitForInit(): Promise<void> {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
getSnapshot(): RuntimeConfigSnapshot {
|
|
return {
|
|
apiEndpoint: this.apiEndpoint,
|
|
apiPublicEndpoint: this.apiPublicEndpoint,
|
|
gatewayEndpoint: this.gatewayEndpoint,
|
|
mediaEndpoint: this.mediaEndpoint,
|
|
staticCdnEndpoint: this.staticCdnEndpoint,
|
|
marketingEndpoint: this.marketingEndpoint,
|
|
adminEndpoint: this.adminEndpoint,
|
|
inviteEndpoint: this.inviteEndpoint,
|
|
giftEndpoint: this.giftEndpoint,
|
|
webAppEndpoint: this.webAppEndpoint,
|
|
gifProvider: this.gifProvider,
|
|
gifProviderDisplayName: this.gifProviderDisplayName,
|
|
gifAttributionRequired: this.gifAttributionRequired,
|
|
captchaProvider: this.captchaProvider,
|
|
hcaptchaSiteKey: this.hcaptchaSiteKey,
|
|
turnstileSiteKey: this.turnstileSiteKey,
|
|
apiCodeVersion: this.apiCodeVersion,
|
|
features: {...this.features},
|
|
sso: this.sso ? {...this.sso} : null,
|
|
registration: {...this.registration},
|
|
community: {...this.community},
|
|
services: {...this.services},
|
|
publicPushVapidKey: this.publicPushVapidKey,
|
|
limits: this.cloneLimits(this.limits),
|
|
appPublic: normalizeAppPublicConfig(this.appPublic),
|
|
};
|
|
}
|
|
|
|
private createEmptyLimitConfig(): LimitConfigSnapshot {
|
|
return {
|
|
version: 1,
|
|
traitDefinitions: [],
|
|
rules: [],
|
|
};
|
|
}
|
|
|
|
private cloneLimits(limits: LimitConfigSnapshot): LimitConfigSnapshot {
|
|
return JSON.parse(JSON.stringify(limits));
|
|
}
|
|
|
|
private normalizeLimits(limits?: LimitConfigSnapshot): LimitConfigSnapshot {
|
|
const cloned = this.cloneLimits(limits ?? this.createEmptyLimitConfig());
|
|
return {
|
|
...cloned,
|
|
traitDefinitions: cloned.traitDefinitions ?? [],
|
|
rules: cloned.rules ?? [],
|
|
};
|
|
}
|
|
|
|
private processLimitsFromApi(limits: LimitConfigSnapshot | LimitConfigWireFormat | undefined): LimitConfigSnapshot {
|
|
if (limits && 'defaultsHash' in limits && limits.version === 2) {
|
|
const expanded = expandWireFormat(limits);
|
|
this.currentDefaultsHash = limits.defaultsHash;
|
|
return this.normalizeLimits(expanded);
|
|
}
|
|
this.currentDefaultsHash = null;
|
|
return this.normalizeLimits(limits as LimitConfigSnapshot | undefined);
|
|
}
|
|
|
|
applyAdminInstanceConfig(config: InstanceConfigResponse): void {
|
|
const appPublic = normalizeAppPublicConfig({
|
|
branding: config.app_public.branding,
|
|
setup: {
|
|
...config.app_public.setup,
|
|
admin_url: this.appPublic.setup.admin_url,
|
|
},
|
|
legal: config.app_public.legal,
|
|
registration: config.app_public.registration,
|
|
});
|
|
runInAction(() => {
|
|
this.features = {
|
|
...this.features,
|
|
self_hosted: config.self_hosted,
|
|
};
|
|
this.registration = normalizeInstanceRegistration(config.registration);
|
|
this.community = normalizeInstanceCommunity({
|
|
single_community: config.policy.single_community_enabled,
|
|
single_community_guild_id: config.policy.single_community_enabled
|
|
? config.policy.single_community_guild_id
|
|
: null,
|
|
direct_messages_disabled: config.policy.direct_messages_disabled,
|
|
});
|
|
this.services = normalizeInstanceServices({
|
|
gif_enabled: config.policy.services_resolved.gif_enabled,
|
|
youtube_enabled: config.policy.services_resolved.youtube_enabled,
|
|
bluesky_enabled: config.policy.services_resolved.bluesky_enabled,
|
|
});
|
|
this.appPublic = appPublic;
|
|
});
|
|
applyDocumentBranding(appPublic);
|
|
}
|
|
|
|
private updateFromInstance(instance: InstanceDiscoveryResponse): void {
|
|
this.assertCodeVersion(instance.api_code_version);
|
|
const apiEndpoint = instance.endpoints.api_client ?? instance.endpoints.api;
|
|
const apiPublicEndpoint = instance.endpoints.api_public ?? apiEndpoint;
|
|
const sso = instance.sso ?? null;
|
|
const appPublic = normalizeAppPublicConfig(instance.app_public);
|
|
const gifProviderInfo = normalizeGifProviderInfo({
|
|
provider: instance.gif?.provider,
|
|
attributionRequired: instance.gif?.attribution_required,
|
|
});
|
|
runInAction(() => {
|
|
this.apiEndpoint = apiEndpoint;
|
|
this.apiPublicEndpoint = apiPublicEndpoint;
|
|
this.gatewayEndpoint = instance.endpoints.gateway;
|
|
this.mediaEndpoint = instance.endpoints.media;
|
|
this.staticCdnEndpoint = instance.endpoints.static_cdn;
|
|
this.marketingEndpoint = instance.endpoints.marketing;
|
|
this.adminEndpoint = instance.endpoints.admin;
|
|
this.inviteEndpoint = instance.endpoints.invite;
|
|
this.giftEndpoint = instance.endpoints.gift;
|
|
this.webAppEndpoint = instance.endpoints.webapp;
|
|
this.gifProvider = gifProviderInfo.name;
|
|
this.gifProviderDisplayName = gifProviderInfo.displayName;
|
|
this.gifAttributionRequired = gifProviderInfo.attributionRequired;
|
|
this.captchaProvider = instance.captcha.provider;
|
|
this.hcaptchaSiteKey = instance.captcha.hcaptcha_site_key;
|
|
this.turnstileSiteKey = instance.captcha.turnstile_site_key;
|
|
this.apiCodeVersion = instance.api_code_version;
|
|
this.features = {
|
|
...DEFAULT_INSTANCE_FEATURES,
|
|
...instance.features,
|
|
};
|
|
this.sso = sso;
|
|
this.registration = normalizeInstanceRegistration(instance.registration);
|
|
this.community = normalizeInstanceCommunity(instance.community);
|
|
this.services = normalizeInstanceServices(instance.services);
|
|
this.publicPushVapidKey = instance.push?.public_vapid_key ?? null;
|
|
this.limits = this.processLimitsFromApi(instance.limits);
|
|
this.appPublic = appPublic;
|
|
});
|
|
applyDocumentBranding(appPublic);
|
|
}
|
|
|
|
private assertCodeVersion(instanceVersion: number): void {
|
|
if (instanceVersion < API_CODE_VERSION) {
|
|
throw new Error(
|
|
`Incompatible server (code version ${instanceVersion}); this client requires ${API_CODE_VERSION}.`,
|
|
);
|
|
}
|
|
}
|
|
|
|
get webAppBaseUrl(): string {
|
|
if (this.webAppEndpoint) {
|
|
return this.webAppEndpoint.replace(/\/$/, '');
|
|
}
|
|
try {
|
|
const url = new URL(this.apiEndpoint);
|
|
if (url.pathname.endsWith('/api')) {
|
|
url.pathname = url.pathname.slice(0, -4) || '/';
|
|
}
|
|
return url.toString().replace(/\/$/, '');
|
|
} catch {
|
|
return this.apiEndpoint.replace(/\/api$/, '');
|
|
}
|
|
}
|
|
|
|
isSelfHosted(): boolean {
|
|
return DeveloperOptions.selfHostedModeOverride || this.features.self_hosted;
|
|
}
|
|
|
|
get emailsEnabled(): boolean {
|
|
return this.features.emails_enabled;
|
|
}
|
|
|
|
get productName(): string {
|
|
return this.appPublic.branding.product_name.trim() || DEFAULT_APP_PUBLIC_CONFIG.branding.product_name;
|
|
}
|
|
|
|
get iconUrl(): string | null {
|
|
return this.appPublic.branding.icon_url;
|
|
}
|
|
|
|
get symbolUrl(): string | null {
|
|
return this.appPublic.branding.symbol_url ?? this.iconUrl;
|
|
}
|
|
|
|
get logoUrl(): string | null {
|
|
return this.appPublic.branding.logo_url ?? this.iconUrl;
|
|
}
|
|
|
|
get wordmarkUrl(): string | null {
|
|
return this.appPublic.branding.wordmark_url;
|
|
}
|
|
|
|
get faviconUrl(): string | null {
|
|
return this.appPublic.branding.favicon_url ?? this.iconUrl;
|
|
}
|
|
|
|
get themeColor(): string | null {
|
|
return this.appPublic.branding.theme_color;
|
|
}
|
|
|
|
get termsUrl(): string | null {
|
|
return this.appPublic.legal.terms_url;
|
|
}
|
|
|
|
get privacyUrl(): string | null {
|
|
return this.appPublic.legal.privacy_url;
|
|
}
|
|
|
|
get collectDateOfBirthOnRegistration(): boolean {
|
|
return this.appPublic.registration.collect_date_of_birth;
|
|
}
|
|
|
|
get setupAdminUrl(): string | null {
|
|
return this.appPublic.setup.admin_url;
|
|
}
|
|
|
|
requiresSelfHostedSetup(): boolean {
|
|
return this.isSelfHosted() && !this.appPublic.setup.configured;
|
|
}
|
|
|
|
get singleCommunityEnabled(): boolean {
|
|
return this.community.single_community;
|
|
}
|
|
|
|
get singleCommunityGuildId(): string | null {
|
|
return this.community.single_community ? this.community.single_community_guild_id : null;
|
|
}
|
|
|
|
get directMessagesDisabled(): boolean {
|
|
return this.community.direct_messages_disabled;
|
|
}
|
|
|
|
get gifEnabled(): boolean {
|
|
return this.services.gif_enabled;
|
|
}
|
|
|
|
get blueskyConnectionsEnabled(): boolean {
|
|
return this.services.bluesky_enabled;
|
|
}
|
|
|
|
get marketingHost(): string {
|
|
try {
|
|
return new URL(this.marketingEndpoint).host;
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
get inviteHost(): string {
|
|
try {
|
|
return new URL(this.inviteEndpoint).host;
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
get giftHost(): string {
|
|
try {
|
|
return new URL(this.giftEndpoint).host;
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
get inviteUrlBase(): string {
|
|
try {
|
|
const url = new URL(this.inviteEndpoint);
|
|
const path = url.pathname !== '/' ? url.pathname.replace(/\/$/, '') : '';
|
|
return `${url.host}${path}`;
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
get giftUrlBase(): string {
|
|
try {
|
|
const url = new URL(this.giftEndpoint);
|
|
const path = url.pathname !== '/' ? url.pathname.replace(/\/$/, '') : '';
|
|
return `${url.host}${path}`;
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
applyGifProviderHeaders(input: GifProviderInfoInput): void {
|
|
const info = normalizeGifProviderInfo(input);
|
|
if (
|
|
this.gifProvider === info.name &&
|
|
this.gifProviderDisplayName === info.displayName &&
|
|
this.gifAttributionRequired === info.attributionRequired
|
|
) {
|
|
return;
|
|
}
|
|
runInAction(() => {
|
|
this.gifProvider = info.name;
|
|
this.gifProviderDisplayName = info.displayName;
|
|
this.gifAttributionRequired = info.attributionRequired;
|
|
});
|
|
}
|
|
|
|
get localInstanceDomain(): string {
|
|
try {
|
|
const url = new URL(this.apiEndpoint);
|
|
return url.hostname;
|
|
} catch {
|
|
return 'localhost';
|
|
}
|
|
}
|
|
}
|
|
|
|
export default new RuntimeConfig();
|