mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
refactor: remove dead code (#3337)
This commit is contained in:
1 parent
a19002652b
commit
93d1f8af46
1527 files changed
+21170
-73846
No files matched your search
@@ -135,62 +135,6 @@ export class AvatarService {
|
||||
return storedHash;
|
||||
}
|
||||
|
||||
async uploadAvatarToPath(params: {
|
||||
bucket: string;
|
||||
keyPath: string;
|
||||
errorPath: string;
|
||||
previousKey?: string | null;
|
||||
base64Image?: string | null;
|
||||
}): Promise<string | null> {
|
||||
const {bucket, keyPath, errorPath, previousKey, base64Image} = params;
|
||||
const stripAnimationPrefix = (key: string) => (key.startsWith('a_') ? key.substring(2) : key);
|
||||
if (!base64Image) {
|
||||
if (previousKey) {
|
||||
await this.storageService.deleteObject(bucket, `${keyPath}/${stripAnimationPrefix(previousKey)}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const base64Data = base64Image.includes(',') ? base64Image.split(',')[1] : base64Image;
|
||||
const imageBuffer = new Uint8Array(Buffer.from(base64Data, 'base64'));
|
||||
const maxAvatarSize = this.resolveSizeLimit('avatar_max_size', AVATAR_MAX_SIZE);
|
||||
if (imageBuffer.length > maxAvatarSize) {
|
||||
throw InputValidationError.fromCode(errorPath, ValidationErrorCodes.IMAGE_SIZE_EXCEEDS_LIMIT, {
|
||||
maxSize: maxAvatarSize,
|
||||
});
|
||||
}
|
||||
const metadata = this.requireAllowedMetadata({
|
||||
metadata: await this.mediaService.getMetadata({
|
||||
type: 'base64',
|
||||
base64: base64Data,
|
||||
version: 2,
|
||||
nsfw: 'allow',
|
||||
}),
|
||||
kind: 'avatar',
|
||||
errorPath,
|
||||
});
|
||||
const imageHash = crypto.createHash('md5').update(Buffer.from(imageBuffer)).digest('hex');
|
||||
const imageHashShort = imageHash.slice(0, 8);
|
||||
const isAnimatedAvatar = metadata.animated ?? false;
|
||||
const storedHash = isAnimatedAvatar ? `a_${imageHashShort}` : imageHashShort;
|
||||
if (bannedAvatarHashCache.contains(imageHashShort)) {
|
||||
throw new ContentBlockedError();
|
||||
}
|
||||
await this.scanAndBlockBannedSha({
|
||||
imageBuffer,
|
||||
resourceType: 'other',
|
||||
});
|
||||
const uploadBuffer = await this.stripImageMetadata(imageBuffer, metadata.format);
|
||||
await this.storageService.uploadObject({
|
||||
bucket,
|
||||
key: `${keyPath}/${imageHashShort}`,
|
||||
body: uploadBuffer,
|
||||
});
|
||||
if (previousKey && stripAnimationPrefix(previousKey) !== imageHashShort) {
|
||||
await this.storageService.deleteObject(bucket, `${keyPath}/${stripAnimationPrefix(previousKey)}`);
|
||||
}
|
||||
return storedHash;
|
||||
}
|
||||
|
||||
async processEmoji(params: {errorPath: string; base64Image: string; guildFeatures: Iterable<string>}): Promise<{
|
||||
imageBuffer: Uint8Array;
|
||||
animated: boolean;
|
||||
|
||||
@@ -56,17 +56,6 @@ class ContentModerationService {
|
||||
}
|
||||
}
|
||||
|
||||
scanHostname(host: string, ctx: ModerationContext): void {
|
||||
if (!host) return;
|
||||
if (urlBlocklistCache.isHostnameBanned(host)) {
|
||||
Logger.warn(
|
||||
{surface: ctx.surface, userId: ctx.userId?.toString(), host},
|
||||
'content_moderation.block hostname match',
|
||||
);
|
||||
throw new ContentBlockedError();
|
||||
}
|
||||
}
|
||||
|
||||
scanFileBuffer(buffer: Buffer | Uint8Array, ctx: ModerationContext): string {
|
||||
const sha = createHash('sha256').update(buffer).digest('hex');
|
||||
this.scanSha256(sha, ctx);
|
||||
|
||||
@@ -26,10 +26,6 @@ export class DisabledVirusScanService implements IVirusScanService {
|
||||
};
|
||||
}
|
||||
|
||||
async isVirusHashCached(fileHash: string): Promise<boolean> {
|
||||
return this.cachedVirusHashes.has(fileHash);
|
||||
}
|
||||
|
||||
async cacheVirusHash(fileHash: string): Promise<void> {
|
||||
this.cachedVirusHashes.add(fileHash);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
import {stripOwnAttachmentSignature} from '@app/api/attachment/AttachmentUrls';
|
||||
import type {ChannelID, MessageID} from '@app/api/BrandedTypes';
|
||||
import type {RichEmbedMediaWithMetadata} from '@app/api/channel/EmbedTypes';
|
||||
import type {IChannelRepository} from '@app/api/channel/IChannelRepository';
|
||||
import {nextVersion} from '@app/api/database/CassandraTypes';
|
||||
import type {MessageEmbed, MessageEmbedChild} from '@app/api/database/types/MessageTypes';
|
||||
import {
|
||||
type IMediaService,
|
||||
@@ -35,15 +33,6 @@ import type {
|
||||
} from '@fluxer/schema/src/domains/message/MessageRequestSchemas';
|
||||
import type {IWorkerService} from '@pkgs/worker/src/contracts/IWorkerService';
|
||||
|
||||
interface CreateEmbedsParams {
|
||||
channelId: ChannelID;
|
||||
messageId: MessageID;
|
||||
content: string | null;
|
||||
customEmbeds?: Array<RichEmbedRequest>;
|
||||
guildId: bigint | null;
|
||||
nsfwMode: MediaProxyNsfwMode;
|
||||
}
|
||||
|
||||
interface ProcessedUrlEmbeds {
|
||||
embeds: Array<Embed>;
|
||||
cacheTtlSeconds: number | null;
|
||||
@@ -68,20 +57,11 @@ export class EmbedService {
|
||||
private readonly MAX_EMBED_CHARACTERS_BUG_HUNTER = 12000;
|
||||
|
||||
constructor(
|
||||
private channelRepository: IChannelRepository,
|
||||
private unfurlerService: IUnfurlerService,
|
||||
private mediaService: IMediaService,
|
||||
private workerService: IWorkerService<WorkerTaskName>,
|
||||
) {}
|
||||
|
||||
async createAndSaveEmbeds(params: CreateEmbedsParams): Promise<Array<MessageEmbed> | null> {
|
||||
if (params.customEmbeds?.length) {
|
||||
return await this.processCustomEmbeds(params);
|
||||
} else {
|
||||
return await this.processUrlEmbeds(params);
|
||||
}
|
||||
}
|
||||
|
||||
async getInitialEmbeds(params: {
|
||||
content: string | null;
|
||||
customEmbeds?: Array<RichEmbedRequest>;
|
||||
@@ -116,14 +96,6 @@ export class EmbedService {
|
||||
await this.enqueue(channelId, messageId, guildId, nsfwMode, options);
|
||||
}
|
||||
|
||||
async processUrl(
|
||||
url: string,
|
||||
nsfwMode: MediaProxyNsfwMode = 'block',
|
||||
options: UnfurlOptions = {},
|
||||
): Promise<Array<Embed>> {
|
||||
return (await this.processUrlWithCachePolicy(url, nsfwMode, options)).embeds;
|
||||
}
|
||||
|
||||
async processUrlWithCachePolicy(
|
||||
url: string,
|
||||
nsfwMode: MediaProxyNsfwMode = 'block',
|
||||
@@ -190,39 +162,6 @@ export class EmbedService {
|
||||
return false;
|
||||
}
|
||||
|
||||
private async processCustomEmbeds({
|
||||
channelId,
|
||||
messageId,
|
||||
customEmbeds,
|
||||
nsfwMode,
|
||||
}: CreateEmbedsParams): Promise<Array<MessageEmbed> | null> {
|
||||
if (!customEmbeds?.length) return null;
|
||||
this.validateEmbedSize(customEmbeds);
|
||||
const embeds = await Promise.all(customEmbeds.map((embed) => this.createEmbed(embed, nsfwMode)));
|
||||
await this.updateMessageEmbeds(channelId, messageId, embeds);
|
||||
return embeds.map((embed) => embed.toMessageEmbed());
|
||||
}
|
||||
|
||||
private async processUrlEmbeds({
|
||||
channelId,
|
||||
messageId,
|
||||
content,
|
||||
guildId,
|
||||
nsfwMode,
|
||||
}: CreateEmbedsParams): Promise<Array<MessageEmbed> | null> {
|
||||
if (!content) {
|
||||
await this.updateMessageEmbeds(channelId, messageId, []);
|
||||
return null;
|
||||
}
|
||||
const urls = UnfurlerUtils.extractURLs(content);
|
||||
if (!urls.length) {
|
||||
await this.updateMessageEmbeds(channelId, messageId, []);
|
||||
return null;
|
||||
}
|
||||
await this.enqueue(channelId, messageId, guildId, nsfwMode, {content});
|
||||
return null;
|
||||
}
|
||||
|
||||
private mapResponseEmbed(embed: MessageEmbedResponse): MessageEmbed {
|
||||
return {
|
||||
...this.mapResponseEmbedChild(embed),
|
||||
@@ -529,16 +468,4 @@ export class EmbedService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async updateMessageEmbeds(channelId: ChannelID, messageId: MessageID, embeds: Array<Embed>): Promise<void> {
|
||||
const currentMessage = await this.channelRepository.getMessage(channelId, messageId);
|
||||
if (!currentMessage) return;
|
||||
const currentRow = currentMessage.toRow();
|
||||
const updatedData = {
|
||||
...currentRow,
|
||||
embeds: embeds.length > 0 ? embeds.map((embed) => embed.toMessageEmbed()) : null,
|
||||
version: nextVersion(currentRow.version),
|
||||
};
|
||||
await this.channelRepository.upsertMessage(updatedData, currentRow);
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ export function callCallerRpcParams(caller: CallCaller | undefined): Record<stri
|
||||
return {caller_id: caller.id, caller_name: caller.name, caller_avatar: caller.avatar};
|
||||
}
|
||||
|
||||
export interface GatewayGuildMemoryStatsEntry {
|
||||
interface GatewayGuildMemoryStatsEntry {
|
||||
node_id: string;
|
||||
guild_id: string | null;
|
||||
guild_name: string;
|
||||
@@ -52,13 +52,13 @@ export interface GatewayGuildMemoryStats {
|
||||
guilds: Array<GatewayGuildMemoryStatsEntry>;
|
||||
}
|
||||
|
||||
export interface GatewayNodeMemoryStats {
|
||||
interface GatewayNodeMemoryStats {
|
||||
total: string;
|
||||
processes: string;
|
||||
system: string;
|
||||
}
|
||||
|
||||
export interface GatewayNodeStatsEntry {
|
||||
interface GatewayNodeStatsEntry {
|
||||
node_id: string;
|
||||
status: string;
|
||||
sessions: number;
|
||||
@@ -91,12 +91,12 @@ export interface GatewayNodeStats {
|
||||
nodes: Array<GatewayNodeStatsEntry>;
|
||||
}
|
||||
|
||||
export interface GatewayVoiceStateRegionCount {
|
||||
interface GatewayVoiceStateRegionCount {
|
||||
region_id: string;
|
||||
voice_state_count: number;
|
||||
}
|
||||
|
||||
export interface GatewayVoiceStateServerCount {
|
||||
interface GatewayVoiceStateServerCount {
|
||||
server_id: string;
|
||||
voice_state_count: number;
|
||||
}
|
||||
|
||||
@@ -71,14 +71,14 @@ interface ListParticipantsParams {
|
||||
serverId: string;
|
||||
}
|
||||
|
||||
export interface ListParticipantsSuccess {
|
||||
interface ListParticipantsSuccess {
|
||||
status: 'ok';
|
||||
participants: Array<{
|
||||
identity: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ListParticipantsError {
|
||||
interface ListParticipantsError {
|
||||
status: 'error';
|
||||
errorCode: string;
|
||||
retryable: boolean;
|
||||
|
||||
@@ -16,21 +16,21 @@ interface MediaProxyMetadataPolicy {
|
||||
nsfw: MediaProxyNsfwMode;
|
||||
}
|
||||
|
||||
export type MediaProxyMetadataExternalRequest = {
|
||||
type MediaProxyMetadataExternalRequest = {
|
||||
type: 'external';
|
||||
url: string;
|
||||
with_base64?: boolean;
|
||||
} & MediaProxyMetadataPolicy;
|
||||
export type MediaProxyMetadataUploadRequest = {
|
||||
type MediaProxyMetadataUploadRequest = {
|
||||
type: 'upload';
|
||||
upload_filename: string;
|
||||
filename?: string;
|
||||
} & MediaProxyMetadataPolicy;
|
||||
export type MediaProxyMetadataBase64Request = {
|
||||
type MediaProxyMetadataBase64Request = {
|
||||
type: 'base64';
|
||||
base64: string;
|
||||
} & MediaProxyMetadataPolicy;
|
||||
export type MediaProxyMetadataS3Request = {
|
||||
type MediaProxyMetadataS3Request = {
|
||||
type: 's3';
|
||||
bucket: string;
|
||||
key: string;
|
||||
@@ -72,7 +72,7 @@ export type MediaProxyFrameRequest =
|
||||
key: string;
|
||||
};
|
||||
|
||||
export interface MediaProxyFrameData {
|
||||
interface MediaProxyFrameData {
|
||||
timestamp: number;
|
||||
mime_type: string;
|
||||
base64: string;
|
||||
|
||||
@@ -230,15 +230,6 @@ export class KVAccountDeletionQueueService {
|
||||
}
|
||||
}
|
||||
|
||||
async getStateVersion(): Promise<number | null> {
|
||||
try {
|
||||
return parseStoredTimestamp(await this.kvClient.get(STATE_VERSION_KEY), 'Deletion queue state version');
|
||||
} catch (error) {
|
||||
Logger.error({error}, 'Failed to get state version');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private getSecondaryKey(userId: UserID): string {
|
||||
return `deletion_queue_by_user:${userId.toString()}`;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
buildProcessedMediaObject,
|
||||
stripNonJpegImageMetadataForUpload,
|
||||
} from '@app/api/infrastructure/StorageObjectHelpers';
|
||||
import {stripNonJpegImageMetadataForUpload} from '@app/api/infrastructure/StorageObjectHelpers';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
const PNG_SIGNATURE = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
||||
@@ -118,9 +115,3 @@ describe('stripNonJpegImageMetadataForUpload for WebP', () => {
|
||||
expect(stripped.body).toEqual(webp([riffChunk('VP8X', expectedVp8x), riffChunk('ANIM', new Uint8Array(6)), anmf]));
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildProcessedMediaObject', () => {
|
||||
it('leaves non-media objects for plain copy', async () => {
|
||||
await expect(buildProcessedMediaObject(textBytes('plain text'), 'text/plain')).resolves.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -20,15 +20,6 @@ interface ProcessedMediaFile {
|
||||
height?: number;
|
||||
}
|
||||
|
||||
interface ProcessedMediaObject {
|
||||
body: Uint8Array;
|
||||
contentType: string;
|
||||
contentHash: string;
|
||||
contentLength: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
interface StrippedMediaBuffer {
|
||||
body: Uint8Array;
|
||||
contentType: string;
|
||||
@@ -36,65 +27,6 @@ interface StrippedMediaBuffer {
|
||||
height?: number;
|
||||
}
|
||||
|
||||
function processedMediaObject(
|
||||
body: Uint8Array,
|
||||
contentType: string,
|
||||
dimensions?: {width: number; height: number},
|
||||
): ProcessedMediaObject {
|
||||
return {
|
||||
body,
|
||||
contentType,
|
||||
contentHash: createHash('sha256').update(body).digest('hex'),
|
||||
contentLength: body.length,
|
||||
...dimensions,
|
||||
};
|
||||
}
|
||||
|
||||
async function processJpegData(sourceData: Uint8Array, contentType: string): Promise<ProcessedMediaObject> {
|
||||
const inputPath = temporaryFile({extension: 'jpg'});
|
||||
const outputPath = temporaryFile({extension: 'jpg'});
|
||||
try {
|
||||
await fs.promises.writeFile(inputPath, sourceData);
|
||||
const metadata = await sharp(sourceData).metadata();
|
||||
const needsRotation = metadata.orientation != null && metadata.orientation > 1;
|
||||
let preserveIcc = true;
|
||||
if (needsRotation) {
|
||||
const sourceQuality = await getJpegQualityEstimate(inputPath);
|
||||
const chromaSubsampling = metadata.chromaSubsampling === '4:4:4' ? '4:4:4' : '4:2:0';
|
||||
const processedBuffer = await sharp(sourceData)
|
||||
.rotate()
|
||||
.jpeg({quality: sourceQuality ?? 95, chromaSubsampling})
|
||||
.toBuffer();
|
||||
await fs.promises.writeFile(outputPath, processedBuffer);
|
||||
preserveIcc = metadata.space !== 'cmyk';
|
||||
} else {
|
||||
await fs.promises.copyFile(inputPath, outputPath);
|
||||
}
|
||||
await stripJpegMetadata(outputPath, inputPath, {preserveIcc, rotated: needsRotation});
|
||||
const finalBuffer = await fs.promises.readFile(outputPath);
|
||||
const dimensions =
|
||||
metadata.width && metadata.height
|
||||
? metadata.orientation != null && metadata.orientation >= 5 && metadata.orientation <= 8
|
||||
? {width: metadata.height, height: metadata.width}
|
||||
: {width: metadata.width, height: metadata.height}
|
||||
: undefined;
|
||||
const processed = processedMediaObject(new Uint8Array(finalBuffer), contentType, dimensions);
|
||||
const cleanupErrors = await cleanupTempFiles([inputPath, outputPath]);
|
||||
if (cleanupErrors.length > 0) {
|
||||
throw new Error(
|
||||
`Failed to cleanup temporary files: ${cleanupErrors.map((e) => e.path).join(', ')}. This may indicate disk space or permission issues.`,
|
||||
);
|
||||
}
|
||||
return processed;
|
||||
} catch (error) {
|
||||
const cleanupErrors = await cleanupTempFiles([inputPath, outputPath]);
|
||||
if (cleanupErrors.length > 0) {
|
||||
Logger.error({cleanupErrors, originalError: error}, 'Failed to cleanup temp files after operation failure');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeContentType(contentType: string): string {
|
||||
return contentType.split(';', 1)[0]?.trim().toLowerCase() || 'application/octet-stream';
|
||||
}
|
||||
@@ -107,24 +39,6 @@ function isJpegContentType(contentType: string): boolean {
|
||||
return contentType.includes('jpeg') || contentType.includes('jpg');
|
||||
}
|
||||
|
||||
function contentTypeToMediaExtension(contentType: string): string {
|
||||
if (contentType.includes('mp4') || contentType.includes('mpeg4')) return 'mp4';
|
||||
if (contentType.includes('webm')) return 'webm';
|
||||
if (contentType.includes('quicktime')) return 'mov';
|
||||
if (contentType.includes('x-matroska')) return 'mkv';
|
||||
if (contentType.includes('avi')) return 'avi';
|
||||
if (contentType.includes('flv')) return 'flv';
|
||||
if (contentType.includes('mp2t')) return 'ts';
|
||||
if (contentType.includes('mpeg')) return contentType.startsWith('audio/') ? 'mp3' : 'mpeg';
|
||||
if (contentType.includes('x-ms-wmv')) return 'wmv';
|
||||
if (contentType.includes('wav')) return 'wav';
|
||||
if (contentType.includes('flac')) return 'flac';
|
||||
if (contentType.includes('aac')) return 'aac';
|
||||
if (contentType.includes('aiff')) return 'aiff';
|
||||
if (contentType.includes('ogg')) return 'ogg';
|
||||
return 'mp4';
|
||||
}
|
||||
|
||||
function imageContentTypeForSharpFormat(format: string | undefined, fallback: string): string {
|
||||
switch (format) {
|
||||
case 'png':
|
||||
@@ -314,53 +228,6 @@ async function stripMetadataWithExiftool(data: Uint8Array, extension: string): P
|
||||
}
|
||||
}
|
||||
|
||||
async function stripMediaMetadata(data: Uint8Array, contentType: string): Promise<StrippedMediaBuffer | null> {
|
||||
const normalizedContentType = normalizeContentType(contentType);
|
||||
if (!isMediaContentType(normalizedContentType)) return null;
|
||||
if (isJpegContentType(normalizedContentType)) {
|
||||
const processed = await processJpegData(data, normalizedContentType);
|
||||
return {
|
||||
body: processed.body,
|
||||
contentType: processed.contentType,
|
||||
...(processed.width && processed.height ? {width: processed.width, height: processed.height} : {}),
|
||||
};
|
||||
}
|
||||
if (normalizedContentType.startsWith('image/')) {
|
||||
return stripNonJpegImageMetadataForUpload(data, normalizedContentType);
|
||||
}
|
||||
if (normalizedContentType.startsWith('video/') || normalizedContentType.startsWith('audio/')) {
|
||||
return {
|
||||
body: await stripVideoMetadata(data, normalizedContentType),
|
||||
contentType: normalizedContentType,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function buildProcessedMediaObject(
|
||||
data: Uint8Array,
|
||||
contentType: string,
|
||||
): Promise<ProcessedMediaObject | null> {
|
||||
const stripped = await stripMediaMetadata(data, contentType);
|
||||
if (!stripped) return null;
|
||||
const dimensions = stripped.width && stripped.height ? {width: stripped.width, height: stripped.height} : undefined;
|
||||
return processedMediaObject(stripped.body, stripped.contentType, dimensions);
|
||||
}
|
||||
|
||||
async function stripVideoMetadata(data: Uint8Array, contentType: string): Promise<Uint8Array> {
|
||||
const ext = contentTypeToMediaExtension(contentType);
|
||||
const inputPath = temporaryFile({extension: ext});
|
||||
const outputPath = temporaryFile({extension: ext});
|
||||
try {
|
||||
await fs.promises.writeFile(inputPath, data);
|
||||
await stripVideoMetadataInPlace(inputPath, outputPath);
|
||||
const result = await fs.promises.readFile(outputPath);
|
||||
return new Uint8Array(result);
|
||||
} finally {
|
||||
await cleanupTempFiles([inputPath, outputPath]);
|
||||
}
|
||||
}
|
||||
|
||||
async function stripVideoMetadataInPlace(inputPath: string, outputPath: string): Promise<void> {
|
||||
await execFilePromise('ffmpeg', [
|
||||
'-hide_banner',
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
GetObjectCommand,
|
||||
type GetObjectCommandOutput,
|
||||
HeadObjectCommand,
|
||||
type HeadObjectCommandOutput,
|
||||
ListObjectsV2Command,
|
||||
ListPartsCommand,
|
||||
PutObjectCommand,
|
||||
@@ -676,10 +675,6 @@ export class StorageService implements IStorageService {
|
||||
return this.client.send(new GetObjectCommand({Bucket: params.bucket, Key: params.key}));
|
||||
}
|
||||
|
||||
async headObject(params: {bucket: string; key: string}): Promise<HeadObjectCommandOutput> {
|
||||
return this.client.send(new HeadObjectCommand({Bucket: params.bucket, Key: params.key}));
|
||||
}
|
||||
|
||||
async listObjects(params: {bucket: string; prefix: string; maxObjects?: number}): Promise<
|
||||
ReadonlyArray<{
|
||||
key: string;
|
||||
|
||||
@@ -322,7 +322,7 @@ export async function shutdownActivityEvents(timeoutMs = 2000): Promise<void> {
|
||||
stopActivityEvents();
|
||||
}
|
||||
|
||||
export function stopActivityEvents(): void {
|
||||
function stopActivityEvents(): void {
|
||||
runtime?.stop();
|
||||
runtime = null;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export function setActivityProcessChannel(channel: Channel): void {
|
||||
processChannel = channel;
|
||||
}
|
||||
|
||||
export function activityMetaFromContext(ctx: Context<HonoEnv>): ActivityRequestContext {
|
||||
function activityMetaFromContext(ctx: Context<HonoEnv>): ActivityRequestContext {
|
||||
const origin = ctx.req.header('origin');
|
||||
const ua = ctx.req.header('user-agent');
|
||||
const acceptLanguage = ctx.req.header('accept-language');
|
||||
|
||||
@@ -4,10 +4,10 @@ import type {EventKind} from '@app/api/infrastructure/activity/Contract.generate
|
||||
import type {IKVProvider} from '@pkgs/kv_client/src/IKVProvider';
|
||||
|
||||
export const ACTIVITY_SPOOL_KEY = 'activity:spool';
|
||||
export const ACTIVITY_SPOOL_SIGNAL_CAP = 200_000;
|
||||
const ACTIVITY_SPOOL_SIGNAL_CAP = 200_000;
|
||||
export const ACTIVITY_SPOOL_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
const DRAIN_BATCH = 200;
|
||||
export const DRAIN_BUDGET_MS = 15_000;
|
||||
const DRAIN_BUDGET_MS = 15_000;
|
||||
|
||||
export interface ActivitySpoolEntry {
|
||||
kind: EventKind;
|
||||
|
||||
@@ -24,7 +24,7 @@ function isSharedListName(name: string): name is SharedListName {
|
||||
return (SHARED_LISTS as ReadonlyArray<string>).includes(name);
|
||||
}
|
||||
|
||||
export function parseSharedList(value: string): ReadonlySet<string> {
|
||||
function parseSharedList(value: string): ReadonlySet<string> {
|
||||
const entries = new Set<string>();
|
||||
for (const line of value.split('\n')) {
|
||||
const entry = line.trim().toLowerCase();
|
||||
@@ -52,7 +52,7 @@ export function isBlockedEmailDomain(domain: string | null | undefined): boolean
|
||||
return !!tld && sharedListHas('email_tld_blocked', tld);
|
||||
}
|
||||
|
||||
export function sharedListSizes(): Record<SharedListName, number | null> {
|
||||
function sharedListSizes(): Record<SharedListName, number | null> {
|
||||
const sizes = {} as Record<SharedListName, number | null>;
|
||||
for (const name of SHARED_LISTS) {
|
||||
sizes[name] = lists.get(name)?.size ?? null;
|
||||
|
||||
Reference in new issue
Block a user