mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-05-18 05:39:15 +01:00
97 lines
2.9 KiB
TypeScript
97 lines
2.9 KiB
TypeScript
// Copyright 2024 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import { createReadStream } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { protocol } from 'electron';
|
|
|
|
import type { OptionalResourceService } from './OptionalResourceService.main.ts';
|
|
import { getAppRootDir } from '../ts/util/appRootDir.main.ts';
|
|
import { toWebStream } from '../ts/util/toWebStream.node.ts';
|
|
import * as Errors from '../ts/types/errors.std.ts';
|
|
import { createLogger } from '../ts/logging/log.std.ts';
|
|
|
|
const log = createLogger('AssetService');
|
|
|
|
const LOCAL_ASSETS = new Set([
|
|
'fonts/signal-symbols/SignalSymbolsVariable.woff2',
|
|
'fonts/stories/Hatsuishi-Regular.woff2',
|
|
'fonts/stories/EBGaramond-Regular.ttf',
|
|
'fonts/stories/Parisienne-Regular.ttf',
|
|
'fonts/stories/BarlowCondensed-Medium.ttf',
|
|
'fonts/inter-v3.19/Inter-Medium.woff2',
|
|
'fonts/inter-v3.19/Inter-Regular.woff2',
|
|
'fonts/inter-v3.19/Inter-SemiBold.woff2',
|
|
'fonts/inter-v3.19/Inter-BoldItalic.woff2',
|
|
'fonts/inter-v3.19/Inter-Bold.woff2',
|
|
'fonts/inter-v3.19/Inter-Italic.woff2',
|
|
'fonts/inter-v3.19/Inter-SemiBoldItalic.woff2',
|
|
'fonts/mono-special/MonoSpecial-Regular.woff2',
|
|
'fonts/emoji.woff2',
|
|
]);
|
|
|
|
// pathname to optional resource name
|
|
const OPTIONAL_ASSETS = new Map([
|
|
['optional-fonts/emoji-large.woff2', 'emoji-font.woff2'],
|
|
]);
|
|
|
|
export class AssetService {
|
|
readonly #resourceService: OptionalResourceService;
|
|
|
|
private constructor(resourceService: OptionalResourceService) {
|
|
this.#resourceService = resourceService;
|
|
|
|
protocol.handle('asset', async req => {
|
|
const url = new URL(req.url);
|
|
|
|
try {
|
|
return await this.#fetch(url.pathname);
|
|
} catch (error) {
|
|
log.error('protocol handler error', Errors.toLogFormat(error));
|
|
return new Response('internal error', { status: 500 });
|
|
}
|
|
});
|
|
}
|
|
|
|
public static create(resourceService: OptionalResourceService): AssetService {
|
|
return new AssetService(resourceService);
|
|
}
|
|
|
|
async #fetch(pathname: string): Promise<Response> {
|
|
if (!pathname.startsWith('/')) {
|
|
return new Response('invalid pathname', { status: 400 });
|
|
}
|
|
|
|
const path = pathname.slice(1);
|
|
|
|
if (LOCAL_ASSETS.has(path)) {
|
|
const stream = createReadStream(
|
|
join(getAppRootDir(), ...path.split('/'))
|
|
);
|
|
return new Response(toWebStream(stream), {
|
|
status: 200,
|
|
headers: {
|
|
'cache-control': 'public, max-age=2592000, immutable',
|
|
},
|
|
});
|
|
}
|
|
|
|
const optional = OPTIONAL_ASSETS.get(path);
|
|
if (optional == null) {
|
|
return new Response('asset not found', { status: 404 });
|
|
}
|
|
|
|
const asset = await this.#resourceService.getData(optional);
|
|
if (!asset) {
|
|
return new Response('optional asset not found', { status: 404 });
|
|
}
|
|
|
|
return new Response(asset, {
|
|
status: 200,
|
|
headers: {
|
|
'cache-control': 'public, max-age=2592000, immutable',
|
|
},
|
|
});
|
|
}
|
|
}
|