fix: various fixes to things + simply app proxy sentry setup

This commit is contained in:
Hampus Kraft
2026-02-19 00:29:58 +00:00
parent ff1d15f7aa
commit 528e4e0d7f
44 changed files with 441 additions and 1042 deletions

View File

@@ -170,22 +170,6 @@ function stripApiSuffix(url) {
return url.endsWith('/api') ? url.slice(0, -4) : url;
}
function parseSentryDsn(dsn) {
if (!dsn) {
return {};
}
try {
const parsed = new URL(dsn);
const path = parsed.pathname.replace(/^\/+|\/+$/g, '');
const segments = path ? path.split('/') : [];
const projectId = segments.length > 0 ? segments[segments.length - 1] : undefined;
const publicKey = parsed.username || undefined;
return {projectId, publicKey};
} catch {
return {};
}
}
function resolveAppPublic(config) {
const appPublic = getValue(config, ['app_public'], {});
const domain = getValue(config, ['domain'], {});
@@ -193,25 +177,13 @@ function resolveAppPublic(config) {
const endpoints = deriveEndpointsFromDomain(domain, overrides);
const defaultBootstrapEndpoint = endpoints.api;
const defaultPublicEndpoint = stripApiSuffix(endpoints.api);
const sentryDsn =
asString(appPublic.sentry_dsn) ?? asString(getValue(config, ['services', 'app_proxy', 'sentry_dsn']));
const sentryParsed = parseSentryDsn(sentryDsn);
const sentryDsn = asString(appPublic.sentry_dsn);
return {
apiVersion: asString(appPublic.api_version, '1'),
bootstrapApiEndpoint: asString(appPublic.bootstrap_api_endpoint, defaultBootstrapEndpoint),
bootstrapApiPublicEndpoint: asString(appPublic.bootstrap_api_public_endpoint, defaultPublicEndpoint),
relayDirectoryUrl: asString(appPublic.relay_directory_url),
sentryDsn,
sentryProxyPath: asString(
appPublic.sentry_proxy_path,
asString(getValue(config, ['services', 'app_proxy', 'sentry_proxy_path']), '/error-reporting-proxy'),
),
sentryReportHost: asString(
appPublic.sentry_report_host,
asString(getValue(config, ['services', 'app_proxy', 'sentry_report_host']), ''),
),
sentryProjectId: asString(appPublic.sentry_project_id, sentryParsed.projectId),
sentryPublicKey: asString(appPublic.sentry_public_key, sentryParsed.publicKey),
};
}
@@ -262,9 +234,6 @@ export default () => {
PUBLIC_BUILD_TIMESTAMP: buildMetadata.buildTimestamp,
PUBLIC_RELEASE_CHANNEL: buildMetadata.releaseChannel,
PUBLIC_SENTRY_DSN: appPublic.sentryDsn ?? null,
PUBLIC_SENTRY_PROJECT_ID: appPublic.sentryProjectId ?? null,
PUBLIC_SENTRY_PUBLIC_KEY: appPublic.sentryPublicKey ?? null,
PUBLIC_SENTRY_PROXY_PATH: appPublic.sentryProxyPath,
PUBLIC_API_VERSION: appPublic.apiVersion,
PUBLIC_BOOTSTRAP_API_ENDPOINT: appPublic.bootstrapApiEndpoint,
PUBLIC_BOOTSTRAP_API_PUBLIC_ENDPOINT: appPublic.bootstrapApiPublicEndpoint ?? appPublic.bootstrapApiEndpoint,
@@ -482,9 +451,6 @@ export default () => {
'import.meta.env.PUBLIC_BUILD_TIMESTAMP': getPublicEnvVar(publicValues, 'PUBLIC_BUILD_TIMESTAMP'),
'import.meta.env.PUBLIC_RELEASE_CHANNEL': getPublicEnvVar(publicValues, 'PUBLIC_RELEASE_CHANNEL'),
'import.meta.env.PUBLIC_SENTRY_DSN': getPublicEnvVar(publicValues, 'PUBLIC_SENTRY_DSN'),
'import.meta.env.PUBLIC_SENTRY_PROJECT_ID': getPublicEnvVar(publicValues, 'PUBLIC_SENTRY_PROJECT_ID'),
'import.meta.env.PUBLIC_SENTRY_PUBLIC_KEY': getPublicEnvVar(publicValues, 'PUBLIC_SENTRY_PUBLIC_KEY'),
'import.meta.env.PUBLIC_SENTRY_PROXY_PATH': getPublicEnvVar(publicValues, 'PUBLIC_SENTRY_PROXY_PATH'),
'import.meta.env.PUBLIC_API_VERSION': getPublicEnvVar(publicValues, 'PUBLIC_API_VERSION'),
'import.meta.env.PUBLIC_BOOTSTRAP_API_ENDPOINT': getPublicEnvVar(publicValues, 'PUBLIC_BOOTSTRAP_API_ENDPOINT'),
'import.meta.env.PUBLIC_BOOTSTRAP_API_PUBLIC_ENDPOINT': getPublicEnvVar(

View File

@@ -189,7 +189,13 @@ export const PreloadableUserPopout = React.forwardRef<
<Popout
ref={ref}
render={({popoutKey}) => (
<UserProfilePopout popoutKey={popoutKey} user={user} isWebhook={isWebhook} guildId={guildId} />
<UserProfilePopout
key={`${user.id}:${guildId ?? 'global'}:${isWebhook ? 'webhook' : 'user'}`}
popoutKey={popoutKey}
user={user}
isWebhook={isWebhook}
guildId={guildId}
/>
)}
position={position}
tooltip={tooltip}

View File

@@ -129,6 +129,10 @@ export const UserProfileMobileSheet: React.FC = observer(function UserProfileMob
);
const [profile, setProfile] = useState<ProfileRecord | null>(initialProfile);
const [isProfileLoading, setIsProfileLoading] = useState(() => !initialProfile);
const profileMatchesContext = profile?.userId === userId && (profile?.guildId ?? null) === (guildId ?? null);
const activeProfile = profileMatchesContext ? profile : initialProfile;
const isContextSwitching = Boolean(userId) && !activeProfile && !profileMatchesContext;
const shouldShowProfileLoading = (isProfileLoading && !activeProfile) || isContextSwitching;
useEffect(() => {
setProfile(initialProfile);
@@ -136,7 +140,7 @@ export const UserProfileMobileSheet: React.FC = observer(function UserProfileMob
}, [initialProfile]);
useEffect(() => {
if (!userId || profile) {
if (!userId || activeProfile) {
setIsProfileLoading(false);
return;
}
@@ -164,7 +168,7 @@ export const UserProfileMobileSheet: React.FC = observer(function UserProfileMob
return () => {
cancelled = true;
};
}, [userId, guildId, profile]);
}, [userId, guildId, activeProfile]);
useEffect(() => {
if (!guildId || !userId) {
@@ -190,22 +194,24 @@ export const UserProfileMobileSheet: React.FC = observer(function UserProfileMob
return null;
}
const effectiveProfile: ProfileRecord | null = profile ?? mockProfile ?? fallbackProfile;
const effectiveProfile: ProfileRecord | null = activeProfile ?? mockProfile ?? fallbackProfile;
const resolvedProfile: ProfileRecord = effectiveProfile ?? fallbackProfile!;
const userNote = userId ? UserNoteStore.getUserNote(userId) : null;
const handleClose = () => {
store.close();
};
const profileIdentityKey = `${displayUser.id}:${guildId ?? 'global'}`;
return (
<UserProfileMobileSheetContent
key={profileIdentityKey}
user={displayUser}
profile={resolvedProfile}
userNote={userNote}
guildId={guildId}
autoFocusNote={autoFocusNote}
isLoading={isProfileLoading}
isLoading={shouldShowProfileLoading}
onClose={handleClose}
/>
);

View File

@@ -112,32 +112,53 @@ export const UserProfilePopout: React.FC<UserProfilePopoutProps> = observer(
[isWebhook, user.id, guildId, popoutKey],
);
const fetchProfile = useCallback(async () => {
if (isWebhook) return;
const isGuildMember = guildId ? GuildMemberStore.getMember(guildId, user.id) : false;
if (DeveloperOptionsStore.slowProfileLoad) {
await new Promise((resolve) => setTimeout(resolve, 3000));
}
useEffect(() => {
let cancelled = false;
const cachedProfile = UserProfileStore.getProfile(user.id, guildId);
setProfile(cachedProfile ?? createMockProfile(user));
setProfileLoadError(false);
try {
const fetchedProfile = await UserProfileActionCreators.fetch(user.id, isGuildMember ? guildId : undefined);
setProfile(fetchedProfile);
setProfileLoadError(false);
} catch (error) {
logger.error('Failed to fetch profile for user popout', error);
const cachedProfile = UserProfileStore.getProfile(user.id, guildId);
setProfile(cachedProfile ?? createMockProfile(user));
setProfileLoadError(true);
if (isWebhook) {
return () => {
cancelled = true;
};
}
}, [guildId, user.id, isWebhook]);
useEffect(() => {
const fetchProfile = async () => {
const isGuildMember = guildId ? GuildMemberStore.getMember(guildId, user.id) : false;
if (DeveloperOptionsStore.slowProfileLoad) {
await new Promise((resolve) => setTimeout(resolve, 3000));
}
if (cancelled) {
return;
}
try {
const fetchedProfile = await UserProfileActionCreators.fetch(user.id, isGuildMember ? guildId : undefined);
if (cancelled) {
return;
}
setProfile(fetchedProfile);
setProfileLoadError(false);
} catch (error) {
if (cancelled) {
return;
}
logger.error('Failed to fetch profile for user popout', error);
const nextCachedProfile = UserProfileStore.getProfile(user.id, guildId);
setProfile(nextCachedProfile ?? createMockProfile(user));
setProfileLoadError(true);
}
};
fetchProfile();
}, [fetchProfile]);
return () => {
cancelled = true;
};
}, [guildId, isWebhook, user]);
useEffect(() => {
if (profileLoadError && profile) {

View File

@@ -248,7 +248,13 @@ const SpectatorRow = observer(function SpectatorRow({
return (
<Popout
render={({popoutKey}) => (
<UserProfilePopout popoutKey={popoutKey} user={user} isWebhook={false} guildId={guildId} />
<UserProfilePopout
key={`${user.id}:${guildId ?? 'global'}:user`}
popoutKey={popoutKey}
user={user}
isWebhook={false}
guildId={guildId}
/>
)}
position="left-start"
onOpen={onPopoutOpen}

View File

@@ -157,7 +157,13 @@ function VoiceParticipantPopoutRow({entry, guildId, channelId}: VoiceParticipant
return (
<Popout
render={({popoutKey}) => (
<UserProfilePopout popoutKey={popoutKey} user={entry.user} isWebhook={false} guildId={guildId ?? undefined} />
<UserProfilePopout
key={`${entry.user.id}:${guildId ?? 'global'}:user`}
popoutKey={popoutKey}
user={entry.user}
isWebhook={false}
guildId={guildId ?? undefined}
/>
)}
position="left-start"
>

View File

@@ -70,8 +70,6 @@ const logger = new Logger('index');
preloadClientInfo();
const normalizePathSegment = (value: string): string => value.replace(/^\/+|\/+$/g, '');
async function resumePendingDesktopHandoffLogin(): Promise<void> {
const electronApi = getElectronAPI();
if (!electronApi || typeof electronApi.consumeDesktopHandoffCode !== 'function') {
@@ -103,7 +101,7 @@ async function resumePendingDesktopHandoffLogin(): Promise<void> {
}
function initSentry(): void {
const resolvedSentryDsn = buildRuntimeSentryDsn() || RuntimeConfigStore.sentryDsn;
const resolvedSentryDsn = RuntimeConfigStore.sentryDsn;
const normalizedBuildSha =
Config.PUBLIC_BUILD_SHA && Config.PUBLIC_BUILD_SHA !== 'dev' ? Config.PUBLIC_BUILD_SHA : undefined;
const buildNumberString =
@@ -167,24 +165,6 @@ function initSentry(): void {
});
}
function buildRuntimeSentryDsn(): string | null {
if (!RuntimeConfigStore.sentryProjectId || !RuntimeConfigStore.sentryPublicKey) {
return null;
}
const origin = window.location.origin;
if (!origin) {
return null;
}
const proxyPath = normalizePathSegment(RuntimeConfigStore.sentryProxyPath ?? '/error-reporting-proxy');
const projectSegment = normalizePathSegment(RuntimeConfigStore.sentryProjectId);
const url = new URL(`/${proxyPath}/${projectSegment}`, origin);
url.username = RuntimeConfigStore.sentryPublicKey;
return url.toString();
}
async function bootstrap(): Promise<void> {
await initI18n();

View File

@@ -310,10 +310,6 @@ class AccountStorage {
sso: instance.sso,
publicPushVapidKey: instance.publicPushVapidKey,
sentryDsn: instance.sentryDsn ?? '',
sentryProxyPath: instance.sentryProxyPath ?? '',
sentryReportHost: instance.sentryReportHost ?? '',
sentryProjectId: instance.sentryProjectId ?? '',
sentryPublicKey: instance.sentryPublicKey ?? '',
limits:
instance.limits !== undefined && instance.limits !== null
? JSON.parse(JSON.stringify(instance.limits))

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {
MarkdownContext,
type MarkdownRenderOptions,
type RendererProps,
} from '@app/lib/markdown/renderers/RendererTypes';
import {TimestampRenderer} from '@app/lib/markdown/renderers/TimestampRenderer';
import {NodeType, TimestampStyle} from '@fluxer/markdown_parser/src/types/Enums';
import type {TimestampNode} from '@fluxer/markdown_parser/src/types/Nodes';
import {setupI18n} from '@lingui/core';
import React from 'react';
import {renderToStaticMarkup} from 'react-dom/server';
import {describe, expect, test} from 'vitest';
const i18n = setupI18n({locale: 'en-US', messages: {'en-US': {}}});
function createRendererProps(node: TimestampNode): RendererProps<TimestampNode> {
const options: MarkdownRenderOptions = {
context: MarkdownContext.STANDARD_WITHOUT_JUMBO,
shouldJumboEmojis: false,
i18n,
};
return {
node,
id: 'test-timestamp',
renderChildren: () => null,
options,
};
}
describe('TimestampRenderer', () => {
test('renders a non-throwing fallback for invalid timestamps', () => {
const props = createRendererProps({
type: NodeType.Timestamp,
timestamp: Number.POSITIVE_INFINITY,
style: TimestampStyle.ShortDateTime,
});
const renderFn = () => renderToStaticMarkup(React.createElement(TimestampRenderer, props));
expect(renderFn).not.toThrow();
const markup = renderFn();
expect(markup).toContain('Infinity');
expect(markup).not.toContain('<time');
});
test('renders a semantic time element for valid timestamps', () => {
const props = createRendererProps({
type: NodeType.Timestamp,
timestamp: 1618953630,
style: TimestampStyle.ShortDateTime,
});
const markup = renderToStaticMarkup(React.createElement(TimestampRenderer, props));
expect(markup).toContain('<time');
expect(markup).toContain('dateTime="2021-04-20T21:20:30.000Z"');
});
});

View File

@@ -20,6 +20,7 @@
import {Tooltip} from '@app/components/uikit/tooltip/Tooltip';
import type {RendererProps} from '@app/lib/markdown/renderers/RendererTypes';
import {formatTimestamp} from '@app/lib/markdown/utils/DateFormatter';
import {getDateFromUnixTimestampSeconds} from '@app/lib/markdown/utils/TimestampValidation';
import WindowStore from '@app/stores/WindowStore';
import markupStyles from '@app/styles/Markup.module.css';
import timestampRendererStyles from '@app/styles/TimestampRenderer.module.css';
@@ -32,8 +33,7 @@ import {ClockIcon} from '@phosphor-icons/react';
import {clsx} from 'clsx';
import {DateTime} from 'luxon';
import {observer} from 'mobx-react-lite';
import type {ReactElement} from 'react';
import {useEffect, useState} from 'react';
import React, {type ReactElement, useEffect, useState} from 'react';
export const TimestampRenderer = observer(function TimestampRenderer({
node,
@@ -43,25 +43,26 @@ export const TimestampRenderer = observer(function TimestampRenderer({
const {timestamp, style} = node;
const i18n = options.i18n;
const totalMillis = timestamp * 1000;
const date = new Date(totalMillis);
const date = getDateFromUnixTimestampSeconds(timestamp);
const isValidTimestamp = date !== null;
const now = new Date();
const isPast = date < now;
const isFuture = date > now;
const isTodayDate = isSameDay(date);
const isPast = date !== null && date < now;
const isFuture = date !== null && date > now;
const isTodayDate = date !== null && isSameDay(date);
const locale = getCurrentLocale();
const fullDateTime = getFormattedDateTimeWithSeconds(date, locale);
const fullDateTime = date !== null ? getFormattedDateTimeWithSeconds(date, locale) : null;
const isRelativeStyle = style === TimestampStyle.RelativeTime;
const isWindowFocused = WindowStore.focused;
const [relativeDisplayTime, setRelativeDisplayTime] = useState(() => formatTimestamp(timestamp, style, i18n));
const luxonDate = DateTime.fromMillis(totalMillis);
const relativeTime = luxonDate.toRelative();
const [relativeDisplayTime, setRelativeDisplayTime] = useState(() =>
isValidTimestamp ? formatTimestamp(timestamp, style, i18n) : '',
);
const relativeTime = date !== null ? DateTime.fromJSDate(date).toRelative() : null;
useEffect(() => {
if (!isRelativeStyle || !isWindowFocused) {
if (!isValidTimestamp || !isRelativeStyle || !isWindowFocused) {
return;
}
@@ -75,7 +76,11 @@ export const TimestampRenderer = observer(function TimestampRenderer({
refreshDisplay();
const intervalId = setInterval(refreshDisplay, 1000);
return () => clearInterval(intervalId);
}, [isRelativeStyle, isWindowFocused, style, timestamp, i18n]);
}, [isValidTimestamp, isRelativeStyle, isWindowFocused, style, timestamp, i18n]);
if (date === null || fullDateTime === null) {
return React.createElement('span', {className: markupStyles.timestamp}, String(timestamp));
}
const tooltipContent = (
<div className={timestampRendererStyles.tooltipContainer}>

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {formatTimestamp} from '@app/lib/markdown/utils/DateFormatter';
import {TimestampStyle} from '@fluxer/markdown_parser/src/types/Enums';
import {setupI18n} from '@lingui/core';
import {afterEach, describe, expect, test, vi} from 'vitest';
const i18n = setupI18n({locale: 'en-US', messages: {'en-US': {}}});
describe('formatTimestamp', () => {
afterEach(() => {
vi.useRealTimers();
});
test('returns the raw numeric value for non-finite timestamps', () => {
const output = formatTimestamp(Number.POSITIVE_INFINITY, TimestampStyle.ShortDateTime, i18n);
expect(output).toBe('Infinity');
});
test('returns the raw numeric value for out-of-range timestamps', () => {
const output = formatTimestamp(8640000000001, TimestampStyle.ShortDateTime, i18n);
expect(output).toBe('8640000000001');
});
test('still formats valid relative timestamps', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-02-18T12:00:00.000Z'));
const oneMinuteAgoTimestamp = Math.floor(new Date('2026-02-18T11:59:00.000Z').getTime() / 1000);
const output = formatTimestamp(oneMinuteAgoTimestamp, TimestampStyle.RelativeTime, i18n);
expect(output.length).toBeGreaterThan(0);
});
});

View File

@@ -17,6 +17,7 @@
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {getDateFromUnixTimestampSeconds} from '@app/lib/markdown/utils/TimestampValidation';
import {shouldUse12HourFormat} from '@app/utils/DateUtils';
import {getCurrentLocale} from '@app/utils/LocaleUtils';
import {
@@ -33,9 +34,8 @@ import {TimestampStyle} from '@fluxer/markdown_parser/src/types/Enums';
import type {I18n} from '@lingui/core';
import {msg} from '@lingui/core/macro';
function formatRelativeTime(timestamp: number, i18n: I18n): string {
function formatRelativeTime(date: Date, i18n: I18n): string {
const locale = getCurrentLocale();
const date = new Date(timestamp * 1000);
const now = new Date();
if (isSameDay(date, now)) {
@@ -142,9 +142,14 @@ function formatRelativeTime(timestamp: number, i18n: I18n): string {
export function formatTimestamp(timestamp: number, style: TimestampStyle, i18n: I18n): string {
const locale = getCurrentLocale();
const hour12 = shouldUse12HourFormat(locale);
const date = getDateFromUnixTimestampSeconds(timestamp);
if (date == null) {
return String(timestamp);
}
if (style === TimestampStyle.RelativeTime) {
return formatRelativeTime(timestamp, i18n);
return formatRelativeTime(date, i18n);
}
return formatTimestampWithStyle(timestamp, style, locale, hour12);

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {getDateFromUnixTimestampSeconds} from '@app/lib/markdown/utils/TimestampValidation';
import {describe, expect, test} from 'vitest';
describe('getDateFromUnixTimestampSeconds', () => {
test('returns a valid date for normal unix timestamps', () => {
const result = getDateFromUnixTimestampSeconds(1618953630);
expect(result).toBeInstanceOf(Date);
expect(result?.toISOString()).toBe('2021-04-20T21:20:30.000Z');
});
test('returns null for infinity', () => {
expect(getDateFromUnixTimestampSeconds(Number.POSITIVE_INFINITY)).toBeNull();
});
test('returns null for NaN', () => {
expect(getDateFromUnixTimestampSeconds(Number.NaN)).toBeNull();
});
test('returns null when timestamp is beyond js date range', () => {
expect(getDateFromUnixTimestampSeconds(8640000000001)).toBeNull();
});
});

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
const MILLISECONDS_PER_SECOND = 1000;
export function getDateFromUnixTimestampSeconds(timestamp: number): Date | null {
if (!Number.isFinite(timestamp)) {
return null;
}
const timestampMillis = timestamp * MILLISECONDS_PER_SECOND;
if (!Number.isFinite(timestampMillis)) {
return null;
}
const date = new Date(timestampMillis);
if (Number.isNaN(date.getTime())) {
return null;
}
return date;
}

View File

@@ -243,10 +243,6 @@ class InstanceConfigStore {
push: {public_vapid_key: RuntimeConfigStore.publicPushVapidKey},
appPublic: {
sentry_dsn: RuntimeConfigStore.sentryDsn,
sentry_proxy_path: RuntimeConfigStore.sentryProxyPath,
sentry_report_host: RuntimeConfigStore.sentryReportHost,
sentry_project_id: RuntimeConfigStore.sentryProjectId,
sentry_public_key: RuntimeConfigStore.sentryPublicKey,
},
federation: null,
publicKey: null,

View File

@@ -69,10 +69,6 @@ export interface InstancePush {
export interface InstanceAppPublic {
sentry_dsn: string;
sentry_proxy_path: string;
sentry_report_host: string;
sentry_project_id: string;
sentry_public_key: string;
}
export type GifProvider = 'klipy' | 'tenor';
@@ -110,10 +106,6 @@ export interface RuntimeConfigSnapshot {
publicPushVapidKey: string | null;
limits: LimitConfigSnapshot;
sentryDsn: string;
sentryProxyPath: string;
sentryReportHost: string;
sentryProjectId: string;
sentryPublicKey: string;
relayDirectoryUrl: string | null;
}
@@ -160,10 +152,6 @@ class RuntimeConfigStore {
currentDefaultsHash: string | null = null;
sentryDsn: string = '';
sentryProxyPath: string = '/error-reporting-proxy';
sentryReportHost: string = '';
sentryProjectId: string = '';
sentryPublicKey: string = '';
relayDirectoryUrl: string | null = Config.PUBLIC_RELAY_DIRECTORY_URL;
@@ -237,10 +225,6 @@ class RuntimeConfigStore {
'limits',
'currentDefaultsHash',
'sentryDsn',
'sentryProxyPath',
'sentryReportHost',
'sentryProjectId',
'sentryPublicKey',
'relayDirectoryUrl',
]);
@@ -302,10 +286,6 @@ class RuntimeConfigStore {
this.currentDefaultsHash = null;
this.sentryDsn = snapshot.sentryDsn;
this.sentryProxyPath = snapshot.sentryProxyPath;
this.sentryReportHost = snapshot.sentryReportHost;
this.sentryProjectId = snapshot.sentryProjectId;
this.sentryPublicKey = snapshot.sentryPublicKey;
this.relayDirectoryUrl = snapshot.relayDirectoryUrl;
}
@@ -331,10 +311,6 @@ class RuntimeConfigStore {
publicPushVapidKey: this.publicPushVapidKey,
limits: this.cloneLimits(this.limits),
sentryDsn: this.sentryDsn,
sentryProxyPath: this.sentryProxyPath,
sentryReportHost: this.sentryReportHost,
sentryProjectId: this.sentryProjectId,
sentryPublicKey: this.sentryPublicKey,
relayDirectoryUrl: this.relayDirectoryUrl,
};
}
@@ -472,10 +448,6 @@ class RuntimeConfigStore {
if (instance.app_public) {
this.sentryDsn = instance.app_public.sentry_dsn;
this.sentryProxyPath = instance.app_public.sentry_proxy_path;
this.sentryReportHost = instance.app_public.sentry_report_host;
this.sentryProjectId = instance.app_public.sentry_project_id;
this.sentryPublicKey = instance.app_public.sentry_public_key;
}
});
}

View File

@@ -114,10 +114,6 @@ vi.mock('@app/lib/HttpClient', () => {
limits: {version: 1, traitDefinitions: [], rules: []},
app_public: {
sentry_dsn: '',
sentry_proxy_path: '',
sentry_report_host: '',
sentry_project_id: '',
sentry_public_key: '',
},
};