nodeshiftmusicburmalda/services/api.ts

126 lines
4.2 KiB
TypeScript
Raw Normal View History

2026-04-27 21:01:17 +05:00
import * as SecureStore from 'expo-secure-store';
const BASE = 'https://music.nodeshift.space';
const CLOUD = 'https://cloud.nodeshift.space';
const CLIENT_ID = 'nsc_A5zi4cZ103aYJGcNTG1bERXV1YCAQW_U';
const REDIRECT = 'burmalda://auth/callback';
// ── Token storage ──────────────────────────────────────────────────────────────
export async function saveTokens(access: string, refresh: string) {
await SecureStore.setItemAsync('access_token', access);
await SecureStore.setItemAsync('refresh_token', refresh);
}
export async function getAccessToken(): Promise<string | null> {
return SecureStore.getItemAsync('access_token');
}
export async function clearTokens() {
await SecureStore.deleteItemAsync('access_token');
await SecureStore.deleteItemAsync('refresh_token');
}
// ── Auth fetch helper ──────────────────────────────────────────────────────────
async function apiFetch(path: string, opts: RequestInit = {}): Promise<Response> {
const token = await getAccessToken();
const headers: HeadersInit = {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...(opts.headers as Record<string, string> || {}),
};
return fetch(`${BASE}${path}`, { ...opts, headers });
}
// ── OAuth ──────────────────────────────────────────────────────────────────────
export function buildOAuthUrl(state: string): string {
const p = new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT,
state,
scope: 'profile email',
});
return `${CLOUD}/api/cloud/oauth/authorize?${p}`;
}
export async function exchangeCode(code: string): Promise<{ access_token: string; refresh_token: string; user: User }> {
const res = await fetch(`${BASE}/api/auth/cloud-login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, redirect_uri: REDIRECT }),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.detail || 'Auth failed');
}
return res.json();
}
// ── Types ──────────────────────────────────────────────────────────────────────
export interface Track {
id: number;
title: string;
artist: string;
duration: number;
cover_url: string | null;
stream_url: string;
album?: string;
plays?: number;
liked?: boolean;
}
export interface Album {
id: number;
title: string;
artist: string;
cover_url: string | null;
track_count: number;
}
export interface User {
id: number;
email: string;
username: string | null;
avatar_url: string | null;
plan: string;
}
// ── API calls ──────────────────────────────────────────────────────────────────
export async function getTracks(): Promise<Track[]> {
const r = await apiFetch('/api/music/tracks');
if (!r.ok) return [];
return r.json();
}
export async function getAlbums(): Promise<Album[]> {
const r = await apiFetch('/api/music/albums');
if (!r.ok) return [];
return r.json();
}
export async function getMe(): Promise<User | null> {
const r = await apiFetch('/api/music/profile/me');
if (!r.ok) return null;
return r.json();
}
export async function searchTracks(q: string): Promise<Track[]> {
const r = await apiFetch(`/api/music/tracks?q=${encodeURIComponent(q)}`);
if (!r.ok) return [];
return r.json();
}
export function streamUrl(filename: string): string {
return `${BASE}/api/music/stream/${filename}`;
}
export function coverUrl(filename: string | null): string | null {
if (!filename) return null;
if (filename.startsWith('http')) return filename;
return `${BASE}/api/music/cover/${filename}`;
}