Introduce AssetService

This commit is contained in:
Fedor Indutny
2026-04-08 11:14:04 -07:00
committed by GitHub
parent 662e10831d
commit 91e0f526da
6 changed files with 113 additions and 22 deletions
+83
View File
@@ -0,0 +1,83 @@
// 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';
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',
]);
// pathname to optional resource name
const OPTIONAL_ASSETS = new Map<string, string>([]);
export class AssetService {
readonly #resourceService: OptionalResourceService;
private constructor(resourceService: OptionalResourceService) {
this.#resourceService = resourceService;
protocol.handle('asset', async req => {
const url = new URL(req.url);
return this.#fetch(url.pathname);
});
}
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',
},
});
}
}
+2
View File
@@ -84,6 +84,7 @@ import {
import { SystemTraySettingCache } from './SystemTraySettingCache.node.ts';
import { OptionalResourceService } from './OptionalResourceService.main.ts';
import { EmojiService } from './EmojiService.main.ts';
import { AssetService } from './AssetService.main.ts';
import {
SystemTraySetting,
shouldMinimizeToSystemTray,
@@ -2082,6 +2083,7 @@ app.on('ready', async () => {
join(userDataPath, 'optionalResources')
);
await EmojiService.create(resourceService);
AssetService.create(resourceService);
if (!resolvedTranslationsLocale) {
preferredSystemLocales = resolveCanonicalLocales(
+15 -9
View File
@@ -17,7 +17,7 @@
content="default-src 'none';
child-src 'self';
connect-src 'self' https: wss: attachment:;
font-src 'self';
font-src 'self' asset:;
form-action 'self';
frame-src 'none';
img-src 'self' blob: data: emoji: attachment:;
@@ -29,56 +29,56 @@
<title>Signal</title>
<link
rel="preload"
href="fonts/inter-v3.19/Inter-BoldItalic.woff2"
href="asset:///fonts/inter-v3.19/Inter-BoldItalic.woff2"
as="font"
crossorigin
type="font/woff2"
/>
<link
rel="preload"
href="fonts/inter-v3.19/Inter-Bold.woff2"
href="asset:///fonts/inter-v3.19/Inter-Bold.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="fonts/inter-v3.19/Inter-SemiBoldItalic.woff2"
href="asset:///fonts/inter-v3.19/Inter-SemiBoldItalic.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="fonts/inter-v3.19/Inter-Italic.woff2"
href="asset:///fonts/inter-v3.19/Inter-Italic.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="fonts/inter-v3.19/Inter-SemiBold.woff2"
href="asset:///fonts/inter-v3.19/Inter-SemiBold.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="fonts/inter-v3.19/Inter-Regular.woff2"
href="asset:///fonts/inter-v3.19/Inter-Regular.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="fonts/inter-v3.19/Inter-Medium.woff2"
href="asset:///fonts/inter-v3.19/Inter-Medium.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="fonts/signal-symbols/SignalSymbolsVariable.woff2"
href="asset:///fonts/signal-symbols/SignalSymbolsVariable.woff2"
as="font"
type="font/woff2"
crossorigin
@@ -90,6 +90,12 @@
crossorigin
/>
<link rel="preload" href="images/signal-logo.svg" as="image" crossorigin />
<link
rel="preload"
href="images/emoji-sheet-32.webp"
as="image"
crossorigin
/>
<link
rel="preload"
href="images/emoji-sheet-64.webp"
+11 -11
View File
@@ -3,59 +3,59 @@
@font-face {
font-family: 'Inter';
src: url('../fonts/inter-v3.19/Inter-BoldItalic.woff2');
src: url('asset:///fonts/inter-v3.19/Inter-BoldItalic.woff2');
font-weight: bolder;
font-style: italic;
}
@font-face {
font-family: 'Inter';
src: url('../fonts/inter-v3.19/Inter-Bold.woff2');
src: url('asset:///fonts/inter-v3.19/Inter-Bold.woff2');
font-weight: bolder;
}
@font-face {
font-family: 'Inter';
src: url('../fonts/inter-v3.19/Inter-SemiBoldItalic.woff2');
src: url('asset:///fonts/inter-v3.19/Inter-SemiBoldItalic.woff2');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: 'Inter';
src: url('../fonts/inter-v3.19/Inter-Italic.woff2');
src: url('asset:///fonts/inter-v3.19/Inter-Italic.woff2');
font-style: italic;
}
@font-face {
font-family: 'Inter';
src: url('../fonts/inter-v3.19/Inter-SemiBold.woff2');
src: url('asset:///fonts/inter-v3.19/Inter-SemiBold.woff2');
font-weight: bold;
}
@font-face {
font-family: 'Inter';
src: url('../fonts/inter-v3.19/Inter-Medium.woff2');
src: url('asset:///fonts/inter-v3.19/Inter-Medium.woff2');
font-weight: 500;
}
@font-face {
font-family: 'Inter';
src: url('../fonts/inter-v3.19/Inter-Regular.woff2');
src: url('asset:///fonts/inter-v3.19/Inter-Regular.woff2');
}
@font-face {
font-family: 'Parisienne';
src: url('../fonts/stories/Parisienne-Regular.ttf');
src: url('asset:///fonts/stories/Parisienne-Regular.ttf');
}
@font-face {
font-family: 'Barlow Condensed';
src: url('../fonts/stories/BarlowCondensed-Medium.ttf');
src: url('asset:///fonts/stories/BarlowCondensed-Medium.ttf');
}
@font-face {
font-family: 'EB Garamond';
src: url('../fonts/stories/EBGaramond-Regular.ttf');
src: url('asset:///fonts/stories/EBGaramond-Regular.ttf');
}
@font-face {
font-family: 'Hatsuishi';
src: url('../fonts/stories/Hatsuishi-Regular.woff2');
src: url('asset:///fonts/stories/Hatsuishi-Regular.woff2');
}
+1 -1
View File
@@ -247,7 +247,7 @@
font-style: normal;
font-weight: 300 400 700;
font-display: block;
src: url('../fonts/signal-symbols/SignalSymbolsVariable.woff2');
src: url('asset:///fonts/signal-symbols/SignalSymbolsVariable.woff2');
}
@layer base {
+1 -1
View File
@@ -44,7 +44,7 @@ async function drawImage(
async function getFont(text: string): Promise<string> {
const font = new window.FontFace(
'Inter',
'url("fonts/inter-v3.19/Inter-Regular.woff2")'
'url("asset:///fonts/inter-v3.19/Inter-Regular.woff2")'
);
await font.load();