feat: add personal playlists and shareable track links
release / release (версия и заметки) (push) Successful in 6s
release / android (APK в релиз) (push) Successful in 4m8s

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
mrleo1nidandCursor committed 2026-10-21 23:37:53 +03:00
1 parent 3a097c15c8
commit fa79394869
46 files changed
+2307 -292

No files matched your search

+2 -2
View File
@@ -93,8 +93,8 @@ export class AuthSession {
private readonly listeners = new Set<(s: AuthState) => void>();
constructor(opts: AuthSessionOptions) {
if (opts.clientType === 'android' && !opts.refreshStore) {
throw new Error('AuthSession: android client needs a refreshStore');
if (opts.clientType !== 'web' && !opts.refreshStore) {
throw new Error(`AuthSession: ${opts.clientType} client needs a refreshStore`);
}
this.opts = opts;
this.baseFetch = opts.fetch ?? ((...args) => globalThis.fetch(...args));
+1
View File
@@ -22,5 +22,6 @@ export {
type Track,
} from './player';
export { normalizeGain, TARGET_LUFS } from './gain';
export { trackShareUrl } from './share';
export { checkServer, normalizeServerUrl, type ServerCheck } from './server';
export { initialWaveState, mix, rgb, rgba, step, WAVE_COLORS, waveScale, type Rgb, type WaveState } from './wave-anim';
+9
View File
@@ -0,0 +1,9 @@
import { describe, expect, it } from 'vitest';
import { trackShareUrl } from './share';
describe('trackShareUrl', () => {
it('builds a page URL without stream credentials', () => {
expect(trackShareUrl('https://music.example', 'ab/cd')).toBe('https://music.example/track/ab%2Fcd');
});
});
+4
View File
@@ -0,0 +1,4 @@
/** Public page URL: never includes playback credentials or stream URLs. */
export function trackShareUrl(server: string, trackId: string): string {
return new URL(`/track/${encodeURIComponent(trackId)}`, server).href;
}