feat(social): show friend presence and require online listening
ci / api / api (push) Successful in 1m11s
ci / db / db (push) Failing after 23s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m29s
ci / ts / ts (packages + web) (push) Successful in 3m15s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m27s

This commit is contained in:
mrleo1nid committed 2026-10-23 12:52:00 +03:00
1 parent 582c0dc3d4
commit cd84ceecb2
26 files changed
+574 -400

No files matched your search

+4 -1
View File
@@ -2357,12 +2357,15 @@ components:
playlist_name: {type: string}
SocialPerson:
type: object
required: [id, username, display_name, relationship, unread]
required: [id, username, display_name, relationship, unread, online]
properties:
id: {type: string, format: uuid}
username: {type: string}
display_name: {type: string}
relationship: {type: string, enum: [none, incoming, outgoing, friend, blocked]}
online:
type: boolean
description: A current friend has an active client heartbeat within 45 seconds. Independent of music visibility; false for non-friends and blocked people.
unread: {type: integer}
activity: {$ref: '#/components/schemas/ListeningActivity'}
last_message: {$ref: '#/components/schemas/SocialMessagePreview'}
@@ -36,7 +36,7 @@ export function ChatConversation({ person, api, queryKey, userId, server, active
const cancelEdit=()=>{setText(beforeEdit.current);setEditing(null);};
const mutate=(m:DirectMessage,body:ChatMessageUpdate)=>{if(!update.isPending)update.mutate({id:m.id,body});setMenu(null);};
return <KeyboardAvoidingView behavior="padding" style={{flex:1}}>
<View style={{padding:12,gap:8}}><H1>{person.display_name}</H1><Field label={t('social.chat_search')} value={search} onChangeText={setSearch} maxLength={100}/><ErrorBox>{messages.error||send.error||update.error?errorText(messages.error??send.error??update.error):null}</ErrorBox></View>
<View style={{padding:12,gap:8}}><H1>{person.display_name}</H1><Muted>{t(person.online ? 'social.online' : 'social.offline')}</Muted><Field label={t('social.chat_search')} value={search} onChangeText={setSearch} maxLength={100}/><ErrorBox>{messages.error||send.error||update.error?errorText(messages.error??send.error??update.error):null}</ErrorBox></View>
<ScrollView ref={scroll} keyboardShouldPersistTaps="handled" contentContainerStyle={{padding:14,gap:12}} onContentSizeChange={()=>{if(bottom&&!q)scroll.current?.scrollToEnd({animated:!reduced});}}>
{messages.hasNextPage&&<Button title={t('social.older')} variant="ghost" busy={messages.isFetchingNextPage} onPress={()=>{setBottom(false);void messages.fetchNextPage();}}/>}
{!list.length&&<Muted>{t(messages.isPending?'social.loading':q?'social.chat_no_results':'social.first_message')}</Muted>}
@@ -1,6 +1,6 @@
import { socialApi, type ListeningPreferences, type SocialPerson } from '@pipu/core';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Modal, ScrollView, Switch, Text, View } from 'react-native';
import { useSession, useSessionCtx, useUser } from '../../lib/session';
@@ -32,7 +32,8 @@ export function ListeningPanel() {
}
export function ListenTogetherButton({ person }: { person: SocialPerson }) {
const { t } = useTranslation(); const [open, setOpen] = useState(false);
return <><Button title={t('social.together')} variant="ghost" onPress={() => setOpen(true)} />{open && <TogetherSheet person={person} close={() => setOpen(false)} />}</>;
useEffect(() => { if (!person.online) setOpen(false); }, [person.online]);
return <><Button title={t('social.together')} disabled={!person.online} variant="ghost" onPress={() => setOpen(true)} />{!person.online && <Muted>{t('social.together_offline')}</Muted>}{open && person.online && <TogetherSheet person={person} close={() => setOpen(false)} />}</>;
}
function TogetherSheet({ person, close }: { person: SocialPerson; close: () => void }) {
const { t } = useTranslation(); const { api, key } = useListening(); const cache = useQueryClient(); const state = usePlayerState(); const errorText = useErrorText();
@@ -123,6 +123,7 @@ export function SocialScreen({ root = false }: { root?: boolean }) {
{!people.length && inbox.data && !results.isFetching && <Muted>{t(q.length >= 2 ? 'social.no_results' : 'social.empty')}</Muted>}
{people.map(p => <View key={p.id} style={{ padding: 16, borderRadius: 16, backgroundColor: colors.surface, gap: 10 }}>
<Text style={{ color: colors.text, fontWeight: '600', fontSize: 17 }}>{p.display_name}</Text><Muted>@{p.username} · {t(`social.rel_${p.relationship}`)}</Muted>
{p.relationship === 'friend' && <Text style={{ color: colors.text2, fontSize: 12 }}>{t(p.online ? 'social.online' : 'social.offline')}</Text>}
{p.activity && <View style={{ borderLeftWidth: 2, borderLeftColor: colors.ok, paddingLeft: 12, gap: 6 }}>
<Text style={{ color: colors.ok, fontSize: 12 }}>{t('social.listening')}</Text>
<Pressable accessibilityRole="link" onPress={() => router.push({ pathname: '/social-track', params: { id: p.activity!.track_id } })}><Text style={{ color: colors.accent2 }}>{p.activity.track_title}</Text></Pressable>
+2
View File
@@ -17,6 +17,8 @@ for (const width of [320, 800, 1280]) {
await openFriends();
if (width === 800) await page.evaluate(() => { document.documentElement.dataset.theme = 'light'; });
await page.getByRole('button', { name: /moderator Начать разговор/ }).click();
await expect(page.getByRole('region', { name: 'Написать', exact: true }).getByText('Не в сети', { exact: true })).toBeVisible();
await expect(page.getByRole('region', { name: 'Написать', exact: true }).getByRole('button', { name: 'Слушать вместе', exact: true })).toBeDisabled();
await expect(page.getByRole('region', { name: 'Написать', exact: true })).toHaveCSS('animation-name', width === 1280 ? /conversationIn/ : 'none');
if (width === 800) expect((await page.getByRole('textbox', { name: 'Сообщение', exact: true }).boundingBox())!.width).toBeGreaterThan(180);
await page.getByRole('textbox', { name: 'Сообщение', exact: true }).fill('Привет! Нашёл музыку для нашей поездки 🎵');
+24 -2
View File
@@ -15,6 +15,28 @@ import { albumTracks, fakeApi, makeUser, type FakeUser } from './test/fakeApi';
afterEach(cleanup);
describe('social', () => {
it('enables listening together only while a friend is online, independently of music privacy', async () => {
const alice = makeUser('alice', 'user'); const bob = makeUser('bob', 'user');
const { api, user, queryClient } = await renderApp([alice, bob], '/friends', { signedInAs: alice });
api.friendships.set([alice.id, bob.id].sort().join(':'), { sender: bob.id, accepted: true });
await queryClient.invalidateQueries({ queryKey: ['social'] });
const conversation = await screen.findByRole('region', { name: 'Написать' });
expect(within(conversation).getByText('Не в сети')).toBeInTheDocument();
expect(within(conversation).getByRole('button', { name: 'Слушать вместе' })).toBeDisabled();
api.presence.set(bob.id, Date.now());
await queryClient.invalidateQueries({ queryKey: ['social'] });
await within(conversation).findByText('В сети');
expect(within(conversation).getByRole('button', { name: 'Слушать вместе' })).toBeEnabled();
expect(api.activity.has(bob.id)).toBe(false);
await user.click(within(conversation).getByRole('button', { name: 'Слушать вместе' }));
await screen.findByRole('dialog', { name: 'Слушать вместе' });
api.presence.set(bob.id, Date.now() - 46_000);
await queryClient.invalidateQueries({ queryKey: ['social'] });
await waitFor(() => expect(screen.queryByRole('dialog', { name: 'Слушать вместе' })).not.toBeInTheDocument());
expect(within(conversation).getByRole('button', { name: 'Слушать вместе' })).toBeDisabled();
expect(within(conversation).getByText('Не в сети')).toBeInTheDocument();
});
it('finds music beyond the last text page without acknowledging unread messages', async () => {
const alice = makeUser('alice', 'user'); const bob = makeUser('bob', 'user');
const { api, user, queryClient } = await renderApp([alice, bob], '/friends', { signedInAs: alice });
@@ -113,8 +135,8 @@ describe('social', () => {
it('saves privacy and independent request admission preferences', async () => {
const alice = makeUser('alice', 'user');
const { api, user } = await renderApp([alice], '/friends', { signedInAs: alice });
await user.click(await screen.findByRole('button', { name: 'Мой статус скрыт' }));
const privacy = await screen.findByRole('group', { name: 'Показывать мой статус' });
await user.click(await screen.findByRole('button', { name: 'Моя музыка скрыта' }));
const privacy = await screen.findByRole('group', { name: 'Кто видит мою музыку' });
expect(within(privacy).getByRole('button', { name: 'Никому' })).toHaveAttribute('aria-pressed', 'true');
await user.click(within(privacy).getByRole('button', { name: 'Друзьям' }));
await waitFor(() => expect(api.activityVisibility.get(alice.id)).toBe('friends'));
@@ -9,6 +9,7 @@ import { arrowBackIcon, arrowDownIcon, arrowUpIcon, chatIcon, closeIcon, moreIco
import { Button, EmptyState, Field, FormError, Icon, Menu, Skeleton } from '../../shared/ui/ui';
import { ListenTogetherButton } from './Listening';
import { ChatMusic, MessageText } from './ChatMusic';
import { Presence } from './Presence';
import s from './social.module.css';
export function ChatConversation({ person, api, queryKey, userId, active = true, onBack, actions }: {
@@ -85,8 +86,8 @@ export function ChatConversation({ person, api, queryKey, userId, active = true,
return <section className={s.chat} aria-label={t('social.chat')}>
<header className={s.chatHeader}>
{onBack && <Button className={`${s.iconButton} ${s.backButton}`} variant="ghost" icon={arrowBackIcon} aria-label={t('social.back_to_messages')} onClick={onBack} />}
<span className={`${s.avatar} ${person.activity ? s.avatarListening : ''}`} aria-hidden>{person.display_name.slice(0, 1).toUpperCase()}</span>
<div className={s.chatIdentity}><h2>{person.display_name}</h2><span className={s.muted}>@{person.username}</span></div>
<span className={`${s.avatar} ${person.online ? s.avatarListening : ''}`} aria-hidden>{person.display_name.slice(0, 1).toUpperCase()}</span>
<div className={s.chatIdentity}><h2>{person.display_name}</h2><Presence online={person.online} /></div>
<div className={s.chatTools}><ListenTogetherButton person={person} /><Button variant="ghost" className={s.iconButton} icon={searchIcon} aria-label={t('social.chat_search')} aria-expanded={searchOpen} onClick={() => { setSearchOpen(v => !v); setSearch(''); setQ(''); }} />{actions}</div>
</header>
{person.activity && <div className={s.nowListening}><Icon src={musicIcon} size={18} /><div><span>{t('social.listening')}</span><Link to="/track/$id" params={{ id: person.activity.track_id }}>{person.activity.track_title}{person.activity.artist ? ` · ${person.activity.artist}` : ''}</Link>{person.activity.context === 'playlist' && person.activity.playlist_id && <Link to="/activity/playlist/$userId" params={{ userId: person.id }}>{t('social.from_playlist', { name: person.activity.playlist_name ?? '' })}</Link>}</div></div>}
+3 -2
View File
@@ -1,6 +1,6 @@
import { socialApi, uuidv7, type ListeningPreferences, type SocialPerson } from '@pipu/core';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSession, useUser } from '../../app/session';
import { useDesktop } from '../../desktop';
@@ -34,7 +34,8 @@ export function ListeningPanel({ showPreferences = true, showRequests = true }:
}
export function ListenTogetherButton({ person }: { person: SocialPerson }) {
const { t } = useTranslation(); const [open, setOpen] = useState(false);
return <><Button variant="ghost" icon={waveHeadphonesIcon} aria-label={t('social.together')} onClick={() => setOpen(true)}><span className={s.togetherLabel}>{t('social.together')}</span></Button>{open && <ListenTogetherDialog person={person} close={() => setOpen(false)} />}</>;
useEffect(() => { if (!person.online) setOpen(false); }, [person.online]);
return <><Button disabled={!person.online} title={!person.online ? t('social.together_offline') : undefined} variant="ghost" icon={waveHeadphonesIcon} aria-label={t('social.together')} onClick={() => setOpen(true)}><span className={s.togetherLabel}>{t('social.together')}</span></Button>{open && person.online && <ListenTogetherDialog person={person} close={() => setOpen(false)} />}</>;
}
function ListenTogetherDialog({ person, close }: { person: SocialPerson; close: () => void }) {
const { t } = useTranslation(); const errorText = useErrorText(); const cache = useQueryClient();
@@ -0,0 +1,7 @@
import { useTranslation } from 'react-i18next';
import s from './social.module.css';
export function Presence({ online }: { online: boolean }) {
const { t } = useTranslation();
return <span className={`${s.presence} ${online ? s.presenceOnline : ''}`}><span className={s.presenceDot} aria-hidden />{t(online ? 'social.online' : 'social.offline')}</span>;
}
+4 -3
View File
@@ -11,6 +11,7 @@ import { addIcon, groupIcon, inboxIcon, moreIcon, musicIcon, searchIcon, shieldI
import s from './social.module.css';
import { ListeningPanel, ListenTogetherButton } from './Listening';
import { ChatConversation } from './ChatConversation';
import { Presence } from './Presence';
const scopes = new WeakMap<object, number>();
let nextScope = 0;
@@ -161,8 +162,8 @@ export function SocialPage({ peer = '' }: { peer?: string }) {
{tab === 'people' && q.length >= 2 && results.isFetching && <p className={s.muted} role="status">{t('social.loading')}</p>}
{!shownPeople.length && inbox.data && !results.isFetching && <EmptyState icon={tab === 'requests' ? inboxIcon : groupIcon} action={!search && tab !== 'people' ? <Button onClick={findPeople}>{t('social.find_people')}</Button> : undefined}>{t(search ? 'social.no_results' : tab === 'requests' ? 'social.no_requests' : 'social.empty')}</EmptyState>}
{shownPeople.map(p => tab === 'messages' ? <button key={p.id} type="button" className={`${s.dialogRow} ${selected?.id === p.id ? s.selected : ''}`} aria-pressed={selected?.id === p.id} onClick={() => open(p.id)}>
<span className={`${s.avatar} ${p.activity ? s.avatarListening : ''}`} aria-hidden>{p.display_name.slice(0, 1).toUpperCase()}</span>
<span className={s.dialogBody}><span className={s.dialogTitle}><strong>{p.display_name}</strong>{p.last_message && <time>{new Date(p.last_message.created_at).toLocaleDateString(i18n.language, { day: 'numeric', month: 'short' })}</time>}</span><span className={s.preview}>{p.last_message ? `${p.last_message.sender_id === user.id ? `${t('social.you')}: ` : ''}${p.last_message.deleted ? t('social.message_deleted') : p.last_message.attachment_kind !== 'none' ? t(p.last_message.attachment_kind === 'track' ? 'social.shared_track' : 'social.shared_playlist') : p.last_message.text}` : t('social.start_conversation')}</span>{p.activity && <span className={s.listeningPreview}><Icon src={musicIcon} size={13} />{p.activity.track_title}</span>}</span>
<span className={`${s.avatar} ${p.online ? s.avatarListening : ''}`} aria-hidden>{p.display_name.slice(0, 1).toUpperCase()}</span>
<span className={s.dialogBody}><span className={s.dialogTitle}><strong>{p.display_name}</strong>{p.last_message && <time>{new Date(p.last_message.created_at).toLocaleDateString(i18n.language, { day: 'numeric', month: 'short' })}</time>}</span><span className={s.preview}>{p.last_message ? `${p.last_message.sender_id === user.id ? `${t('social.you')}: ` : ''}${p.last_message.deleted ? t('social.message_deleted') : p.last_message.attachment_kind !== 'none' ? t(p.last_message.attachment_kind === 'track' ? 'social.shared_track' : 'social.shared_playlist') : p.last_message.text}` : t('social.start_conversation')}</span><Presence online={p.online} />{p.activity && <span className={s.listeningPreview}><Icon src={musicIcon} size={13} />{p.activity.track_title}</span>}</span>
{p.unread > 0 && <span className={s.badge}>{p.unread}</span>}
</button> : <Person key={p.id} person={p} selected={peer === p.id} busy={action.isPending} open={() => open(p.id)} act={op => act(p, op)} />)}
</aside>
@@ -190,7 +191,7 @@ function FriendMenu({ person, act }: { person: SocialPerson; act: (a: SocialActi
function Person({ person: p, busy, open, act, selected }: { person: SocialPerson; busy: boolean; selected?: boolean; open: () => void; act: (a: SocialAction['action']) => void }) {
const { t } = useTranslation();
return <article className={`${s.person} ${selected ? s.selected : ''}`}>
<div className={s.identity}><span className={`${s.avatar} ${p.activity ? s.avatarListening : ''}`} aria-hidden>{p.display_name.slice(0, 1).toUpperCase()}</span><div><strong>{p.display_name}</strong><div className={s.muted}>@{p.username} · {t(`social.rel_${p.relationship}`)}</div></div></div>
<div className={s.identity}><span className={`${s.avatar} ${p.online ? s.avatarListening : ''}`} aria-hidden>{p.display_name.slice(0, 1).toUpperCase()}</span><div><strong>{p.display_name}</strong><div className={s.muted}>@{p.username} · {t(`social.rel_${p.relationship}`)}</div>{p.relationship === 'friend' && <Presence online={p.online} />}</div></div>
{p.activity && <div className={s.activity}><span className={s.listening}><Icon src={musicIcon} size={16} />{t('social.listening')}</span><Link to="/track/$id" params={{ id: p.activity.track_id }}>{p.activity.track_title}</Link>{p.activity.artist && <span className={s.muted}>{p.activity.artist}</span>}{p.activity.context === 'playlist' && p.activity.playlist_id && <Link to="/activity/playlist/$userId" params={{ userId: p.id }}>{t('social.from_playlist', { name: p.activity.playlist_name ?? '' })}</Link>}</div>}
<div className={s.actions}>
{p.relationship === 'friend' && <><Button onClick={open}>{t('social.chat')}{p.unread ? ` · ${p.unread}` : ''}</Button><ListenTogetherButton person={p} /></>}
@@ -33,6 +33,9 @@
.listeningPreview { display: flex; align-items: center; gap: 4px; color: var(--ok); font-size: 11px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.avatar { position: relative; width: 42px; height: 42px; flex-shrink: 0; border-radius: 50%; display: grid; place-items: center; color: var(--accent2); background: var(--accent-soft); font-size: 16px; font-weight: 600; }
.avatarListening::after { content: ''; position: absolute; right: 0; bottom: 0; width: 10px; height: 10px; background: var(--ok); border-radius: 50%; border: 2px solid var(--surface); }
.presence { display: inline-flex; align-items: center; gap: 5px; color: var(--text2); font-size: 11px; white-space: nowrap; }
.presenceDot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--text3); flex-shrink: 0; transition: background .2s; }
.presenceOnline .presenceDot { background: var(--ok); }
.directory, .events { flex: 1; min-height: 0; overflow-y: auto; padding: 16px 24px; animation: panelIn .22s ease-out; }
.directory .peopleList { padding: 0; overflow: visible; }
.directory .listSearch { max-width: 440px; margin-left: 0; }
@@ -70,6 +73,7 @@
.chatIdentity { flex: 1; min-width: 0; }
.chatIdentity h2 { font-size: 16px; line-height: 1.4; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chatIdentity > span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
.chatIdentity > .presence { display: inline-flex; }
.chatTools { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
.iconButton { width: 40px; min-width: 40px; height: 40px; padding: 0; border-radius: 12px; flex-shrink: 0; }
.backButton { display: none; }
+5 -1
View File
@@ -90,6 +90,7 @@ export function fakeApi(initial: FakeUser[]) {
const listeningMembers = new Map<string, string>();
const listeningRequests = new Map<string, components['schemas']['ListeningRequest'] & { guest_id: string }>();
const typing = new Map<string, number>();
const presence = new Map<string, number>();
const defaultListening = { requests: 'nobody', auto_accept: 'nobody', autoplay: true } as const;
const pair = (a: string, b: string) => [a, b].sort().join(':');
const isBlocked = (a: string, b: string) => blocks.has(`${a}:${b}`) || blocks.has(`${b}:${a}`);
@@ -100,6 +101,7 @@ export function fakeApi(initial: FakeUser[]) {
return { id: p.id, username: p.username, display_name: p.display_name ?? p.username, ...(visible && activity.has(p.id) && !isBlocked(me,p.id) ? { activity: activity.get(p.id) } : {}),
...(last ? { last_message: { id: last.id, sender_id: last.sender_id, text: last.deleted ? '' : last.text.slice(0, 160), attachment_kind: last.attachment_kind, created_at: last.created_at, deleted: last.deleted ?? false } } : {}),
relationship: blocks.has(`${me}:${p.id}`) ? 'blocked' : f?.accepted ? 'friend' : f ? f.sender === me ? 'outgoing' : 'incoming' : 'none',
online: !!f?.accepted && !isBlocked(me, p.id) && (presence.get(p.id) ?? 0) > Date.now() - 45_000,
unread: messages.filter(m => m.recipient === me && m.sender_id === p.id && !m.read).length };
};
const notify = (recipient: string, actor: FakeUser, kind: components['schemas']['SocialNotification']['kind']) => notifications.unshift({
@@ -244,6 +246,7 @@ export function fakeApi(initial: FakeUser[]) {
}
if (path === '/api/v1/me/social/listening/preferences' && body) { listeningPreferences.set(user.id,body as components['schemas']['ListeningPreferences']);return new Response(null,{status:204}); }
if (path === '/api/v1/me/social/listening') {
presence.set(user.id, Date.now());
const room=listeningRooms.get(listeningMembers.get(user.id) ?? '');
if(room)room.participants=[...listeningMembers].filter(([,id])=>id===room.id).map(([id])=>({id,name:users.get(id)?.display_name??users.get(id)?.username??id}));
return json({preferences:listeningPreferences.get(user.id)??defaultListening,room:room??null,requests:[...listeningRequests.values()].filter(r=>r.recipient_id===user.id||r.sender_id===user.id),server_time:new Date().toISOString()});
@@ -258,6 +261,7 @@ export function fakeApi(initial: FakeUser[]) {
const host=op==='join'?target:user.id; const guest=op==='join'?user.id:target; const prefs=listeningPreferences.get(target)??defaultListening;
const friend=!!friendships.get(pair(user.id,target))?.accepted;
if(isBlocked(user.id,target)||!(prefs.requests==='everyone'||prefs.requests==='friends'&&friend))return problem(403,'social.unavailable');
if ((presence.get(target) ?? 0) <= Date.now() - 45_000) return problem(409, 'social.person_offline');
let room=[...listeningRooms.values()].find(r=>r.owner_id===host);
const source=activity.get(host);if(!room){room={id:crypto.randomUUID(),owner_id:host,owner_name:users.get(host)?.username??host,device_id:String(op==='join'?source?.device_id:body.device_id),track_id:String(op==='join'?source?.track_id:body.track_id),position_ms:Number(op==='join'?source?.position_ms:body.position_ms??0),playing:true,updated_at:new Date().toISOString(),participants:[]};listeningRooms.set(room.id,room);listeningMembers.set(host,room.id);}
if(prefs.auto_accept==='everyone'||prefs.auto_accept==='friends'&&friend)listeningMembers.set(guest,room.id);
@@ -510,5 +514,5 @@ export function fakeApi(initial: FakeUser[]) {
return problem(404, 'common.not_found');
};
return { fetch: fetchImpl as typeof fetch, calls, users, playlists, friendships, notifications, messages, activityVisibility, activity, friendRecommendations, listeningPreferences, listeningRooms, listeningMembers, listeningRequests, signOut: () => (current = null) };
return { presence, fetch: fetchImpl as typeof fetch, calls, users, playlists, friendships, notifications, messages, activityVisibility, activity, friendRecommendations, listeningPreferences, listeningRooms, listeningMembers, listeningRequests, signOut: () => (current = null) };
}
+9
View File
@@ -71,6 +71,15 @@ React Query; Android останавливает polling при уходе при
## Совместное прослушивание
Статус «В сети» виден только текущим друзьям и независим от видимости музыки.
Клиент поддерживает его штатным опросом состояния прослушивания, даже на паузе;
просмотр социального раздела также обновляет собственный heartbeat. Сервер объединяет
частые обновления одного пользователя до одного за 10 секунд. Если ни одно устройство
не отвечает 45 секунд, статус истекает; UI обновляется при следующем опросе друзей.
«Слушать вместе» отключено для друга не в сети. Сервер проверяет присутствие обоих
участников при создании и принятии запроса, поэтому устаревшая кнопка не обходит правило.
Блокировки и ограничения аудитории проверяются до выдачи ошибки об офлайн-статусе.
«Подключиться» отправляет запрос хозяину и оставляет управление у него.
«Пригласить на мой стрим» создаёт собственную комнату: приглашённый становится слушателем.
Один пользователь состоит в одной комнате; несколько слушателей могут подключиться к хозяину.
@@ -1972,6 +1972,8 @@ export interface components {
display_name: string;
/** @enum {string} */
relationship: "none" | "incoming" | "outgoing" | "friend" | "blocked";
/** @description A current friend has an active client heartbeat within 45 seconds. Independent of music visibility; false for non-friends and blocked people. */
online: boolean;
unread: number;
activity?: components["schemas"]["ListeningActivity"];
last_message?: components["schemas"]["SocialMessagePreview"];
+6 -3
View File
@@ -387,13 +387,16 @@
"no_results": "No playlists found"
},
"social": {
"online": "Online",
"offline": "Offline",
"together_offline": "Listen together when your friend is online",
"space": "In your own rhythm",
"requests": "Requests",
"find_people": "Find friends",
"find_conversation": "Find a conversation",
"find_request": "Find a request",
"no_requests": "No requests yet. Incoming and outgoing friend requests will appear here.",
"status_hidden": "My activity is hidden",
"status_hidden": "My music is hidden",
"privacy_settings": "Privacy and social",
"blocked_people": "Blocked people",
"confirm_block": "Block {{name}}?",
@@ -464,11 +467,11 @@
"quiet": "No sounds. New events appear in the counter.",
"share_error": "Could not send. Please try again.",
"attachment_unavailable": "This music is no longer available.",
"activity_visibility": "Show my listening activity",
"activity_visibility": "Who can see my music",
"visibility_nobody": "Nobody",
"visibility_friends": "Friends",
"visibility_everyone": "Everyone",
"activity_privacy_hint": "Hidden by default. Your audience can see the track and open the playlist while you listen.",
"activity_privacy_hint": "Music is hidden by default. Your audience can see the track and open the playlist while you listen. Only friends can see your online status.",
"listening": "Listening now",
"from_playlist": "Playlist “{{name}}”",
"kind_listening_request": "Wants to listen with you",
+1
View File
@@ -124,6 +124,7 @@
"target_changed": "The source changed after checking. Check for updates again."
},
"social": {
"person_offline": "This person is offline. Try inviting them again when they connect",
"invalid": "Check your message or action",
"unavailable": "Chats are available to friends only. The friendship may have ended or the person is unavailable",
"rate_limited": "Too many actions. Please wait a minute",
+6 -3
View File
@@ -387,13 +387,16 @@
"no_results": "Плейлисты не найдены"
},
"social": {
"online": "В сети",
"offline": "Не в сети",
"together_offline": "Слушать вместе можно, когда друг в сети",
"space": "В своём ритме",
"requests": "Заявки",
"find_people": "Найти друзей",
"find_conversation": "Найти диалог",
"find_request": "Найти заявку",
"no_requests": "Новых заявок пока нет. Здесь появятся входящие и отправленные приглашения в друзья.",
"status_hidden": "Мой статус скрыт",
"status_hidden": "Моя музыка скрыта",
"privacy_settings": "Приватность и общение",
"blocked_people": "Заблокированные",
"confirm_block": "Заблокировать {{name}}?",
@@ -464,11 +467,11 @@
"quiet": "Без звуков. Новые события отмечаются счётчиком.",
"share_error": "Не удалось отправить. Попробуйте ещё раз.",
"attachment_unavailable": "Музыка больше недоступна.",
"activity_visibility": "Показывать мой статус",
"activity_visibility": "Кто видит мою музыку",
"visibility_nobody": "Никому",
"visibility_friends": "Друзьям",
"visibility_everyone": "Всем",
"activity_privacy_hint": "По умолчанию статус скрыт. Выбранная аудитория увидит трек и сможет открыть плейлист, пока вы его слушаете.",
"activity_privacy_hint": "По умолчанию музыка скрыта. Выбранная аудитория увидит трек и сможет открыть плейлист, пока вы его слушаете. Статус «В сети» виден только друзьям.",
"listening": "Слушает сейчас",
"from_playlist": "Плейлист «{{name}}»",
"kind_listening_request": "Хочет слушать вместе с вами",
+1
View File
@@ -124,6 +124,7 @@
"target_changed": "Источник изменился после проверки. Проверьте обновления ещё раз."
},
"social": {
"person_offline": "Пользователь не в сети. Повторите приглашение, когда он подключится",
"invalid": "Проверьте сообщение или действие",
"unavailable": "Чат доступен только друзьям. Возможно, дружба завершена или пользователь недоступен",
"rate_limited": "Слишком много действий. Подождите минуту",
+378 -374
View File
@@ -1413,7 +1413,10 @@ type SocialPerson struct {
Id openapi_types.UUID `json:"id"`
// LastMessage Latest message in a current friendship, without marking it read. Deleted content is never returned.
LastMessage *SocialMessagePreview `json:"last_message,omitempty"`
LastMessage *SocialMessagePreview `json:"last_message,omitempty"`
// Online A current friend has an active client heartbeat within 45 seconds. Independent of music visibility; false for non-friends and blocked people.
Online bool `json:"online"`
Relationship SocialPersonRelationship `json:"relationship"`
Unread int `json:"unread"`
Username string `json:"username"`
@@ -14435,380 +14438,381 @@ func (sh *strictHandler) GetReadyz(w http.ResponseWriter, r *http.Request) {
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
"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",
"AA==",
"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",
}
// GetSwagger returns the content of the embedded swagger specification file
@@ -32,6 +32,9 @@ func TestActivityRoomsAndChatPrivacy(t *testing.T) {
require.NoError(t, e)
}
defer pool.Exec(ctx, `DELETE FROM users WHERE id=ANY($1)`, users)
for _, id := range users {
require.NoError(t, s.markPresent(ctx, id))
}
artist, album, track, playlist, device := uuid.New(), uuid.New(), uuid.New(), uuid.New(), uuid.New()
_, e = pool.Exec(ctx, `INSERT INTO artists(id,name,sort_name,name_norm) VALUES($1,'Artist','artist',$2)`, artist, artist.String())
require.NoError(t, e)
+3
View File
@@ -44,6 +44,9 @@ func (h *Handler) GetSocial(ctx context.Context, _ gen.GetSocialRequestObject) (
if e != nil {
return nil, e
}
if e = h.store.markPresent(ctx, id); e != nil {
return nil, e
}
v, e := h.store.inbox(ctx, id)
return gen.GetSocial200JSONResponse(v), e
}
+15 -4
View File
@@ -44,6 +44,9 @@ func (h *Handler) ListeningAction(ctx context.Context, r gen.ListeningActionRequ
if r.Body == nil || r.Body.Id == uuid.Nil || r.Body.TargetId == uuid.Nil {
return nil, invalid()
}
if e = h.store.markPresent(ctx, id); e != nil {
return nil, e
}
e = h.store.listeningAction(ctx, id, *r.Body)
return gen.ListeningAction204Response{}, e
}
@@ -93,6 +96,9 @@ func (s *Store) cleanListening(ctx context.Context) error {
}
func (s *Store) listeningState(ctx context.Context, id uuid.UUID) (gen.ListeningState, error) {
out := gen.ListeningState{Requests: []gen.ListeningRequest{}, ServerTime: time.Now().UTC()}
if e := s.markPresent(ctx, id); e != nil {
return out, e
}
if e := s.cleanListening(ctx); e != nil {
return out, e
}
@@ -134,18 +140,23 @@ func (s *Store) listeningState(ctx context.Context, id uuid.UUID) (gen.Listening
}
func listeningPolicy(ctx context.Context, tx pgx.Tx, recipient, sender uuid.UUID) (bool, bool, error) {
var policy, automatic string
var friends, blocked bool
var friends, blocked, online bool
e := tx.QueryRow(ctx, `SELECT listening_requests,listening_auto_accept,
EXISTS(SELECT 1 FROM friendships WHERE low_id=LEAST($1::uuid,$2::uuid) AND high_id=GREATEST($1::uuid,$2::uuid) AND accepted),
is_blocked OR EXISTS(SELECT 1 FROM users WHERE id=$2 AND is_blocked) OR EXISTS(SELECT 1 FROM social_blocks WHERE (user_id=$1 AND peer_id=$2) OR (user_id=$2 AND peer_id=$1))
FROM users WHERE id=$1`, recipient, sender).Scan(&policy, &automatic, &friends, &blocked)
is_blocked OR EXISTS(SELECT 1 FROM users WHERE id=$2 AND is_blocked) OR EXISTS(SELECT 1 FROM social_blocks WHERE (user_id=$1 AND peer_id=$2) OR (user_id=$2 AND peer_id=$1)),
COALESCE(social_seen_at>now()-interval '45 seconds',false) AND EXISTS(SELECT 1 FROM users WHERE id=$2 AND social_seen_at>now()-interval '45 seconds')
FROM users WHERE id=$1`, recipient, sender).Scan(&policy, &automatic, &friends, &blocked, &online)
if errors.Is(e, pgx.ErrNoRows) {
return false, false, denied()
}
if e != nil {
return false, false, e
}
return !blocked && audienceAllows(policy, friends), !blocked && audienceAllows(automatic, friends), nil
permitted := !blocked && audienceAllows(policy, friends)
if permitted && !online {
return false, false, apperr.Conflict("social.person_offline", "person is offline")
}
return permitted, !blocked && audienceAllows(automatic, friends), nil
}
func admitListening(ctx context.Context, tx pgx.Tx, request, room, guest, recipient, sender uuid.UUID) error {
allowed, _, e := listeningPolicy(ctx, tx, recipient, sender)
+16
View File
@@ -0,0 +1,16 @@
package social
import (
"context"
"github.com/google/uuid"
)
// Existing authenticated social/listening polls serve as heartbeats, including
// while playback is paused or music visibility is off. Coalesce frequent polls
// to one write per account per ten seconds; abandoned clients expire in 45s.
func (s *Store) markPresent(ctx context.Context, id uuid.UUID) error {
_, e := s.pool.Exec(ctx, `UPDATE users SET social_seen_at=now()
WHERE id=$1 AND (social_seen_at IS NULL OR social_seen_at<now()-interval '10 seconds')`, id)
return e
}
@@ -0,0 +1,63 @@
//go:build integration
package social
import (
"context"
"os"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"pipu/api/internal/api/gen"
"pipu/api/internal/platform/db"
)
func TestPresenceAndOfflineInvitations(t *testing.T) {
url := os.Getenv("TEST_DATABASE_URL")
if url == "" {
t.Skip("TEST_DATABASE_URL is not set")
}
ctx := context.Background()
pool, err := db.Connect(ctx, url)
require.NoError(t, err)
defer pool.Close()
require.NoError(t, db.Migrate(ctx, pool))
s := &Store{pool: pool}
a, b, other := uuid.New(), uuid.New(), uuid.New()
ids := []uuid.UUID{a, b, other}
for _, id := range ids {
_, err = pool.Exec(ctx, `INSERT INTO users(id,username,password_hash,listening_requests) VALUES($1,$2,'x','everyone')`, id, id.String())
require.NoError(t, err)
}
defer pool.Exec(ctx, `DELETE FROM users WHERE id=ANY($1)`, ids)
require.NoError(t, s.action(ctx, a, gen.SocialAction{Action: "request", TargetId: b}))
require.NoError(t, s.action(ctx, b, gen.SocialAction{Action: "accept", TargetId: a}))
require.NoError(t, s.markPresent(ctx, a))
inbox, err := s.inbox(ctx, a)
require.NoError(t, err)
require.False(t, inbox.People[0].Online)
// Reject before creating a room, even if an invite bypasses the UI.
require.ErrorContains(t, s.listeningAction(ctx, a, gen.ListeningAction{Id: uuid.New(), Action: "invite", TargetId: b}), "offline")
require.NoError(t, s.markPresent(ctx, b))
inbox, err = s.inbox(ctx, a)
require.NoError(t, err)
require.True(t, inbox.People[0].Online, "online must work with hidden music and paused playback")
require.Nil(t, inbox.People[0].Activity)
found, err := s.people(ctx, other, b.String())
require.NoError(t, err)
require.Len(t, found, 1)
require.False(t, found[0].Online, "non-friends cannot discover presence")
_, err = pool.Exec(ctx, `UPDATE users SET social_seen_at=now()-interval '46 seconds' WHERE id=$1`, b)
require.NoError(t, err)
inbox, err = s.inbox(ctx, a)
require.NoError(t, err)
require.False(t, inbox.People[0].Online)
// Looking at a person's inbox must not revive that person's presence.
require.ErrorContains(t, s.listeningAction(ctx, a, gen.ListeningAction{Id: uuid.New(), Action: "join", TargetId: b}), "offline")
require.NoError(t, s.markPresent(ctx, b))
require.NoError(t, s.action(ctx, a, gen.SocialAction{Action: "block", TargetId: b}))
inbox, err = s.inbox(ctx, a)
require.NoError(t, err)
require.False(t, inbox.People[0].Online, "blocking hides online status")
}
+3 -2
View File
@@ -48,7 +48,8 @@ func allowed(ctx context.Context, tx pgx.Tx, a, b uuid.UUID) error {
const personSQL = `SELECT u.id,u.username,COALESCE(u.display_name,u.username::text),
CASE WHEN b.user_id IS NOT NULL THEN 'blocked' WHEN f.accepted THEN 'friend' WHEN f.sender_id=$1 THEN 'outgoing' WHEN f.sender_id IS NOT NULL THEN 'incoming' ELSE 'none' END,
(SELECT count(*) FROM direct_messages m WHERE m.recipient_id=$1 AND m.sender_id=u.id AND m.read_at IS NULL)
(SELECT count(*) FROM direct_messages m WHERE m.recipient_id=$1 AND m.sender_id=u.id AND m.read_at IS NULL),
COALESCE(f.accepted AND b.user_id IS NULL AND u.social_seen_at>now()-interval '45 seconds',false)
FROM users u
LEFT JOIN friendships f ON f.low_id=LEAST($1::uuid,u.id) AND f.high_id=GREATEST($1::uuid,u.id)
LEFT JOIN social_blocks b ON b.user_id=$1 AND b.peer_id=u.id
@@ -60,7 +61,7 @@ func scanPeople(rows pgx.Rows) ([]gen.SocialPerson, error) {
out := []gen.SocialPerson{}
for rows.Next() {
var p gen.SocialPerson
if e := rows.Scan(&p.Id, &p.Username, &p.DisplayName, &p.Relationship, &p.Unread); e != nil {
if e := rows.Scan(&p.Id, &p.Username, &p.DisplayName, &p.Relationship, &p.Unread, &p.Online); e != nil {
return nil, e
}
out = append(out, p)
@@ -0,0 +1,7 @@
-- +goose Up
-- Reachability is independent of music-sharing privacy. Any active client keeps
-- this lease alive; stopping one device cannot mark another device offline.
ALTER TABLE users ADD COLUMN social_seen_at timestamptz;
-- +goose Down
ALTER TABLE users DROP COLUMN social_seen_at;