- Apple Music: sign MusicKit developer tokens (ES256 via node:crypto),
accept the Music User Token from the iOS app, and poll recently played.
Apple has no now-playing API or play timestamps, so a new top song is
shown as playing for its length; connecting records a baseline so an
old song isn't announced as current
- Last.fm web auth ("Sign in with Last.fm") instead of typing a
username; session key stored encrypted
- Spotify accounts rejected by the 5-user development cap are sent back
with error=spotify_limited instead of a 500, so apps can offer
Spotify via Last.fm
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
import 'dotenv/config';
|
|
import { z } from 'zod';
|
|
|
|
const optional = z
|
|
.string()
|
|
.optional()
|
|
.transform((v) => (v ? v : undefined));
|
|
|
|
const schema = z.object({
|
|
NODE_ENV: z.string().default('development'),
|
|
DATABASE_URL: z.string().default('postgres://musicshare:musicshare@localhost:5447/musicshare'),
|
|
REDIS_URL: z.string().default('redis://localhost:6380'),
|
|
PORT: z.coerce.number().default(4000),
|
|
WEB_URL: z.string().default('http://localhost:3737'),
|
|
API_PUBLIC_URL: z.string().default('http://127.0.0.1:4000'),
|
|
COOKIE_SECURE: z
|
|
.string()
|
|
.default('false')
|
|
.transform((v) => v === 'true'),
|
|
TOKEN_ENCRYPTION_KEY: z
|
|
.string()
|
|
.refine((v) => Buffer.from(v, 'base64').length === 32, 'TOKEN_ENCRYPTION_KEY must be 32 bytes, base64-encoded'),
|
|
SPOTIFY_CLIENT_ID: optional,
|
|
SPOTIFY_CLIENT_SECRET: optional,
|
|
LASTFM_API_KEY: optional,
|
|
/** Enables "Sign in with Last.fm" (web auth). Without it, users type their username. */
|
|
LASTFM_SHARED_SECRET: optional,
|
|
ODESLI_API_KEY: optional,
|
|
/** Apple Music (MusicKit): Team ID, MusicKit key ID, and the key's .p8 contents (newlines may be written as \n). */
|
|
APPLE_TEAM_ID: optional,
|
|
APPLE_MUSICKIT_KEY_ID: optional,
|
|
APPLE_MUSICKIT_PRIVATE_KEY: optional,
|
|
/** Identifies this app to Plex. Keep it stable: changing it invalidates users' Plex authorizations. */
|
|
PLEX_CLIENT_ID: z.string().default('musicshare-dev'),
|
|
/**
|
|
* Allow Jellyfin/Plex server URLs on private networks. Only enable for local
|
|
* dev or self-hosting: in a hosted deployment it lets users make the server
|
|
* request internal addresses (SSRF).
|
|
*/
|
|
ALLOW_PRIVATE_SERVER_URLS: z
|
|
.string()
|
|
.default('false')
|
|
.transform((v) => v === 'true'),
|
|
});
|
|
|
|
export const env = schema.parse(process.env);
|
|
|
|
export const spotifyConfigured = Boolean(env.SPOTIFY_CLIENT_ID && env.SPOTIFY_CLIENT_SECRET);
|
|
export const appleMusicConfigured = Boolean(env.APPLE_TEAM_ID && env.APPLE_MUSICKIT_KEY_ID && env.APPLE_MUSICKIT_PRIVATE_KEY);
|