Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- run: npm run build
- run: npx playwright install --with-deps chromium
- run: npm run test:e2e:pwa
- run: npm run test:e2e:local -- e2e/app.spec.ts e2e/dm-relay-discovery.spec.ts e2e/group-history.spec.ts e2e/public-groups.spec.ts e2e/message-formatting.spec.ts e2e/parity/dm/link-preview.spec.ts
- run: npm run test:e2e:local -- e2e/app.spec.ts e2e/blossom-server-list.spec.ts e2e/dm-relay-discovery.spec.ts e2e/group-history.spec.ts e2e/public-groups.spec.ts e2e/message-formatting.spec.ts e2e/parity/dm/link-preview.spec.ts
- uses: actions/upload-artifact@v4
if: failure()
with:
Expand Down
102 changes: 102 additions & 0 deletions e2e/blossom-server-list.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { expect, test, type Page } from '@playwright/test';
import { finalizeEvent } from 'nostr-tools';
import { WebSocket } from 'ws';
import { bootstrapUser, disposeUsers, E2E_RELAY_URL, TEST_ACCOUNTS } from './parity/helpers';

const DEFAULT_SERVER = 'https://blossom.nostr.build';
const PUBLISHED_SERVER = 'https://published.example.org';
const SECOND_PUBLISHED_SERVER = 'https://second.example.org';
const UPDATED_SERVER = 'https://updated.example.org';

// The app never publishes a server list itself, so the test publishes one the way another
// client would: a signed kind 10063 event with one "server" tag per Blossom server.
async function publishServerList(
privateKey: string,
servers: string[],
createdAt: number,
): Promise<void> {
const socket = new WebSocket(E2E_RELAY_URL);
await new Promise<void>((resolve, reject) => {
socket.once('open', () => resolve());
socket.once('error', reject);
});
const event = finalizeEvent(
{
kind: 10063,
created_at: createdAt,
tags: servers.map((server) => ['server', server]),
content: '',
},
Uint8Array.from(Buffer.from(privateKey, 'hex')),
);
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('Relay accepted no server list')), 10_000);
socket.on('message', (data) => {
const [verb, id] = JSON.parse(data.toString()) as [string, string];
if (verb === 'OK' && id === event.id) {
clearTimeout(timer);
resolve();
}
});
socket.send(JSON.stringify(['EVENT', event]));
});
socket.close();
}

function uploadServer(page: Page) {
return page.evaluate(async () => {
const { useNostrStore } = await import('/src/stores/nostrStore.svelte.ts');
return useNostrStore().getBlossomServerUrl();
});
}

test('uses the Blossom server list published to relays, with the saved setting and default as fallbacks', async ({
browser,
}) => {
const user = await bootstrapUser(browser, TEST_ACCOUNTS.blossomServerList);
try {
const { page } = user;
await page.getByRole('button', { name: 'settings', exact: true }).click();
await page.getByTestId('settings-media-data-storage-item').click();
const serverInput = page.getByTestId('settings-blossom-server-input');
const sourceNote = page.getByTestId('settings-blossom-server-source');

// No list published: the built-in default, and no claim about a published list.
await expect(serverInput).toHaveValue(DEFAULT_SERVER);
await expect(sourceNote).toHaveCount(0);

// A list published while the app is running is picked up without a reload. Relays keep
// only the newest replaceable event, so each list in this test is dated later than the one
// before it, the way a second client publishing a replacement would date it.
const publishedAt = Math.floor(Date.now() / 1000);
await publishServerList(
user.account.privateKey,
[PUBLISHED_SERVER, SECOND_PUBLISHED_SERVER],
publishedAt,
);
await expect.poll(() => uploadServer(page), { timeout: 15_000 }).toBe(PUBLISHED_SERVER);

await publishServerList(user.account.privateKey, [UPDATED_SERVER], publishedAt + 60);
await expect.poll(() => uploadServer(page), { timeout: 15_000 }).toBe(UPDATED_SERVER);
await page.reload();
await expect(serverInput).toBeEnabled();
await expect(serverInput).toHaveValue(UPDATED_SERVER);
await expect(sourceNote).toBeVisible();

// A saved choice still wins over the published list, and restoring the default
// hands the decision back to the list.
await serverInput.fill('https://saved.example.com');
await page.getByTestId('settings-blossom-save').click();
await expect
.poll(() => uploadServer(page), { timeout: 15_000 })
.toBe('https://saved.example.com');
await expect(sourceNote).toHaveCount(0);

await page.getByTestId('settings-blossom-restore-default').click();
await expect.poll(() => uploadServer(page), { timeout: 15_000 }).toBe(UPDATED_SERVER);
await expect(serverInput).toHaveValue(UPDATED_SERVER);
await expect(sourceNote).toBeVisible();
} finally {
await disposeUsers(user);
}
});
30 changes: 24 additions & 6 deletions src/lib/components/settings/PreferenceSettings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,42 @@
export let section: string;
const nostr = useNostrStore();
const startup = toStore(() => nostr.isRestoringStartupState);
const publishedServerList = toStore(() => nostr.blossomServerListVersion);
let dark = readDarkModePreference() ?? document.body.classList.contains('body--dark');
let layout = readDesktopMessageLayoutPreference();
let accent = readThemeAccent();
let saved = nostr.getBlossomServerUrl(),
source = nostr.getBlossomServerOrigin(),
server = saved,
busy = false,
error = '',
notice = '';
let appliedPublishedList = -1;
$: normalized = normalizeBlossomServerUrl(server);
$: if (!$startup && !busy && server === saved) {
function reloadServerFromStore(): void {
saved = nostr.getBlossomServerUrl();
source = nostr.getBlossomServerOrigin();
server = saved;
}
$: if (!$startup && !busy && server === saved) {
reloadServerFromStore();
}
// The published server list can arrive while this screen is open, and it decides the
// server the next upload uses.
$: if (appliedPublishedList !== $publishedServerList) {
appliedPublishedList = $publishedServerList;
if (!$startup && !busy && server === saved) {
reloadServerFromStore();
}
}
async function persist(value: string) {
if (busy) return;
busy = true;
error = '';
notice = '';
try {
saved = await nostr.saveBlossomServerUrl(value);
source = nostr.getBlossomServerOrigin();
server = saved;
notice = $translate('mediaDataStorage.serverSaved');
} catch {
Expand All @@ -49,8 +65,7 @@
function sync() {
accent = readThemeAccent();
if (!busy && server === saved) {
saved = nostr.getBlossomServerUrl();
server = saved;
reloadServerFromStore();
}
}
onMount(() => {
Expand Down Expand Up @@ -157,6 +172,11 @@
autocapitalize="none"
/></label
><small class="settings-caption">{$translate('mediaDataStorage.serverUrlHint')}</small>
{#if source === 'published'}<small
class="settings-caption"
data-testid="settings-blossom-server-source"
>{$translate('mediaDataStorage.serverUrlFromPublishedList')}</small
>{/if}
{#if !normalized}<p class="error">
{$translate(
server.trim()
Expand All @@ -169,9 +189,7 @@
type="button"
class="outline"
data-testid="settings-blossom-restore-default"
disabled={busy ||
$startup ||
(server === DEFAULT_BLOSSOM_SERVER_URL && saved === DEFAULT_BLOSSOM_SERVER_URL)}
disabled={busy || $startup || source !== 'preference'}
onclick={() => persist(DEFAULT_BLOSSOM_SERVER_URL)}
>{$translate('mediaDataStorage.restoreDefault')}</button
><button
Expand Down
1 change: 1 addition & 0 deletions src/lib/nostr/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const NostrKind = {
RelayList: 10002,
DirectMessageReceiveRelayList: 10050,
PrivateStorageRelayList: 10013,
BlossomServerList: 10063,
FollowSet: 30000,
} as const;
export enum NostrRelayStatus {
Expand Down
1 change: 1 addition & 0 deletions src/locales/ar-EG.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "تم حفظ خادم Blossom.",
"mediaDataStorage.serverUrl": "عنوان URL للخادم",
"mediaDataStorage.serverUrlHint": "أدخل عنوان HTTPS الأساسي دون مسار الرفع.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "أدخل عنوان HTTPS صالحًا للخادم دون مسار أو استعلام أو جزء.",
"mediaDataStorage.serverUrlRequired": "يجب إدخال عنوان URL لخادم Blossom.",
"mediaDataStorage.title": "تخزين الوسائط والبيانات",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "تم حفظ خادم Blossom.",
"mediaDataStorage.serverUrl": "عنوان URL للخادم",
"mediaDataStorage.serverUrlHint": "أدخل عنوان HTTPS الأساسي دون مسار الرفع.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "أدخل عنوان HTTPS صالحًا للخادم دون مسار أو استعلام أو جزء.",
"mediaDataStorage.serverUrlRequired": "يجب إدخال عنوان URL لخادم Blossom.",
"mediaDataStorage.title": "تخزين الوسائط والبيانات",
Expand Down
1 change: 1 addition & 0 deletions src/locales/bn-BD.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Blossom সার্ভার সংরক্ষিত হয়েছে।",
"mediaDataStorage.serverUrl": "সার্ভারের URL",
"mediaDataStorage.serverUrlHint": "আপলোডের পথ ছাড়া মূল HTTPS URL লিখুন।",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "পথ, কোয়েরি বা ফ্র্যাগমেন্ট ছাড়া বৈধ HTTPS সার্ভার URL লিখুন।",
"mediaDataStorage.serverUrlRequired": "Blossom সার্ভারের URL প্রয়োজন।",
"mediaDataStorage.title": "মিডিয়া ও ডেটা সংরক্ষণ",
Expand Down
1 change: 1 addition & 0 deletions src/locales/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Blossom-Server gespeichert.",
"mediaDataStorage.serverUrl": "Server-URL",
"mediaDataStorage.serverUrlHint": "Gib die HTTPS-Basis-URL ohne Upload-Pfad ein.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "Gib eine gültige HTTPS-Server-URL ohne Pfad, Abfrage oder Fragment ein.",
"mediaDataStorage.serverUrlRequired": "Eine Blossom-Server-URL ist erforderlich.",
"mediaDataStorage.title": "Medien- und Datenspeicher",
Expand Down
1 change: 1 addition & 0 deletions src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Blossom server saved.",
"mediaDataStorage.serverUrl": "Server URL",
"mediaDataStorage.serverUrlHint": "Enter the HTTPS base URL without an upload path.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "Enter a valid HTTPS server URL without a path, query, or fragment.",
"mediaDataStorage.serverUrlRequired": "A Blossom server URL is required.",
"mediaDataStorage.title": "Media & Data Storage",
Expand Down
1 change: 1 addition & 0 deletions src/locales/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Servidor Blossom guardado.",
"mediaDataStorage.serverUrl": "URL del servidor",
"mediaDataStorage.serverUrlHint": "Introduce la URL HTTPS base sin ruta de subida.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "Introduce una URL HTTPS válida del servidor, sin ruta, consulta ni fragmento.",
"mediaDataStorage.serverUrlRequired": "Se requiere una URL de servidor Blossom.",
"mediaDataStorage.title": "Almacenamiento de archivos y datos",
Expand Down
1 change: 1 addition & 0 deletions src/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Serveur Blossom enregistré.",
"mediaDataStorage.serverUrl": "URL du serveur",
"mediaDataStorage.serverUrlHint": "Saisissez l’URL HTTPS de base sans chemin d’envoi.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "Saisissez une URL de serveur HTTPS valide, sans chemin, paramètres de requête ni fragment.",
"mediaDataStorage.serverUrlRequired": "L’URL d’un serveur Blossom est requise.",
"mediaDataStorage.title": "Stockage des médias et des données",
Expand Down
1 change: 1 addition & 0 deletions src/locales/hi-IN.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Blossom सर्वर सहेजा गया।",
"mediaDataStorage.serverUrl": "सर्वर URL",
"mediaDataStorage.serverUrlHint": "अपलोड पथ के बिना मूल HTTPS URL दर्ज करें।",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "पथ, क्वेरी या फ़्रैगमेंट के बिना मान्य HTTPS सर्वर URL दर्ज करें।",
"mediaDataStorage.serverUrlRequired": "Blossom सर्वर URL आवश्यक है।",
"mediaDataStorage.title": "मीडिया और डेटा भंडारण",
Expand Down
1 change: 1 addition & 0 deletions src/locales/id-ID.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Server Blossom disimpan.",
"mediaDataStorage.serverUrl": "URL server",
"mediaDataStorage.serverUrlHint": "Masukkan URL dasar HTTPS tanpa jalur unggahan.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "Masukkan URL server HTTPS yang valid tanpa jalur, kueri, atau fragmen.",
"mediaDataStorage.serverUrlRequired": "URL server Blossom wajib diisi.",
"mediaDataStorage.title": "Penyimpanan Media dan Data",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Blossom サーバーを保存しました。",
"mediaDataStorage.serverUrl": "サーバー URL",
"mediaDataStorage.serverUrlHint": "アップロード先のパスを含めず、HTTPS のベース URL を入力してください。",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "パス、クエリ、フラグメントを含まない有効な HTTPS サーバー URL を入力してください。",
"mediaDataStorage.serverUrlRequired": "Blossom サーバーの URL が必要です。",
"mediaDataStorage.title": "メディアとデータの保存",
Expand Down
1 change: 1 addition & 0 deletions src/locales/mr-IN.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Blossom सर्व्हर जतन झाला.",
"mediaDataStorage.serverUrl": "सर्व्हर URL",
"mediaDataStorage.serverUrlHint": "अपलोडचा पथ नसलेला मूळ HTTPS URL द्या.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "पथ, क्वेरी किंवा फ्रॅगमेंट नसलेला वैध HTTPS सर्व्हर URL द्या.",
"mediaDataStorage.serverUrlRequired": "Blossom सर्व्हर URL आवश्यक आहे.",
"mediaDataStorage.title": "मीडिया आणि डेटा संचयन",
Expand Down
1 change: 1 addition & 0 deletions src/locales/pcm-NG.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Blossom server don save.",
"mediaDataStorage.serverUrl": "Server URL",
"mediaDataStorage.serverUrlHint": "Enter the main HTTPS URL without upload path.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "Enter correct HTTPS server URL without path, query or fragment.",
"mediaDataStorage.serverUrlRequired": "You need enter Blossom server URL.",
"mediaDataStorage.title": "Media and Data Storage",
Expand Down
1 change: 1 addition & 0 deletions src/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Servidor Blossom salvo.",
"mediaDataStorage.serverUrl": "URL do servidor",
"mediaDataStorage.serverUrlHint": "Insira a URL HTTPS base sem caminho de envio.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "Insira uma URL HTTPS válida de servidor sem caminho, consulta ou fragmento.",
"mediaDataStorage.serverUrlRequired": "É necessária uma URL de servidor Blossom.",
"mediaDataStorage.title": "Armazenamento de mídia e dados",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ru-RU.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Сервер Blossom сохранён.",
"mediaDataStorage.serverUrl": "URL сервера",
"mediaDataStorage.serverUrlHint": "Введите базовый HTTPS URL без пути загрузки.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "Введите корректный HTTPS URL сервера без пути, параметров запроса или фрагмента.",
"mediaDataStorage.serverUrlRequired": "Требуется URL сервера Blossom.",
"mediaDataStorage.title": "Хранение медиа и данных",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ta-IN.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Blossom சேவையகம் சேமிக்கப்பட்டது.",
"mediaDataStorage.serverUrl": "சேவையக URL",
"mediaDataStorage.serverUrlHint": "பதிவேற்றப் பாதை இல்லாத அடிப்படை HTTPS URL ஐ உள்ளிடவும்.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "பாதை, வினவல் அல்லது துண்டு இல்லாத செல்லுபடியான HTTPS சேவையக URL ஐ உள்ளிடவும்.",
"mediaDataStorage.serverUrlRequired": "Blossom சேவையக URL தேவை.",
"mediaDataStorage.title": "ஊடகம் மற்றும் தரவு சேமிப்பு",
Expand Down
1 change: 1 addition & 0 deletions src/locales/te-IN.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Blossom సర్వర్ సేవ్ అయింది.",
"mediaDataStorage.serverUrl": "సర్వర్ URL",
"mediaDataStorage.serverUrlHint": "అప్‌లోడ్ పాత్ లేకుండా ప్రాథమిక HTTPS URL ఇవ్వండి.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "పాత్, క్వెరీ లేదా ఫ్రాగ్మెంట్ లేని చెల్లుబాటు అయ్యే HTTPS సర్వర్ URL ఇవ్వండి.",
"mediaDataStorage.serverUrlRequired": "Blossom సర్వర్ URL అవసరం.",
"mediaDataStorage.title": "మీడియా మరియు డేటా నిల్వ",
Expand Down
1 change: 1 addition & 0 deletions src/locales/tr-TR.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"mediaDataStorage.serverSaved": "Blossom sunucusu kaydedildi.",
"mediaDataStorage.serverUrl": "Sunucu URL’si",
"mediaDataStorage.serverUrlHint": "Yükleme yolu olmadan temel HTTPS URL’sini girin.",
"mediaDataStorage.serverUrlFromPublishedList": "Using the first server from the Blossom list published to your relays.",
"mediaDataStorage.serverUrlInvalid": "Yol, sorgu veya parça içermeyen geçerli bir HTTPS sunucu URL’si girin.",
"mediaDataStorage.serverUrlRequired": "Blossom sunucu URL’si gereklidir.",
"mediaDataStorage.title": "Medya ve Veri Depolama",
Expand Down
Loading