Skip to content

[WIP] Improve Party Mode feature across the entire stack#12

Closed
Copilot wants to merge 43 commits into
mainfrom
copilot/improve-party-mode-feature-again
Closed

[WIP] Improve Party Mode feature across the entire stack#12
Copilot wants to merge 43 commits into
mainfrom
copilot/improve-party-mode-feature-again

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 11, 2026

Thanks for asking me to work on this. I will get started on it and keep this PR's description up to date as I form a plan and make progress.

Original prompt

Overview

Improve the Party Mode feature across the entire stack: the external party server handler (party-mode-handler.js), the local Next.js API (app/api/party/route.ts), and the guest page (app/party/[id]/page.tsx). Also update lib/audio-context.tsx to integrate with the new server endpoints.

Current Architecture

The app uses a split architecture:

  • External Party Server (NEXT_PUBLIC_PARTY_SERVER = https://y-brown-two.vercel.app) — handles queue, currentSong, guest joining/leaving, guest count. This is powered by party-mode-handler.js.
  • Local Next.js API (/api/party at app/api/party/route.ts) — handles chat, votes, reactions, and now WebRTC signaling.

The frontend guest page (app/party/[id]/page.tsx) polls BOTH servers.

Key storage helpers from lib/storage.ts:

  • getGuestId() — returns persistent guest_XXXXXXX string from localStorage
  • getPartyUsername() — returns display name from localStorage

External Party Server Code (party-mode-handler.js)

This is the CURRENT external server handler that needs updating. It's a Node.js/Vercel serverless handler:

// musicana-server — Party Mode v2
const { v4: uuidv4 } = require("uuid")

const PARTY_TTL_MS    = 6 * 60 * 60 * 1000
const MAX_CHAT        = 100
const MAX_REACTIONS   = 80
const REACTION_TTL_MS = 12000

if (!global._parties) global._parties = new Map()
const parties = global._parties

const CORS = {
  "Access-Control-Allow-Origin":   "*",
  "Access-Control-Allow-Methods":  "GET, POST, PUT, DELETE, OPTIONS",
  "Access-Control-Allow-Headers":  "Content-Type",
  "Access-Control-Expose-Headers": "*",
}

function getParty(id) {
  const p = parties.get(id)
  if (!p) return null
  if (Date.now() > p.expiresAt) { parties.delete(id); return null }
  return p
}

function touch(p) {
  p.updatedAt = Date.now()
  p.expiresAt = Date.now() + PARTY_TTL_MS
}

function sortedQueue(queue) {
  return [...queue].sort((a, b) => {
    const scoreA = (a.upvotes || 0) - (a.downvotes || 0)
    const scoreB = (b.upvotes || 0) - (b.downvotes || 0)
    if (scoreB !== scoreA) return scoreB - scoreA
    return (a.addedAt || 0) - (b.addedAt || 0)
  })
}

function publicParty(p) {
  const { hostId, ...pub } = p
  pub.queue = sortedQueue(pub.queue)
  const cutoff = Date.now() - REACTION_TTL_MS
  pub.reactions = (pub.reactions || []).filter(r => r.at > cutoff)
  return pub
}

function readBody(req) {
  return new Promise((resolve) => {
    if (!["POST", "PUT", "PATCH"].includes(req.method)) return resolve({})
    let raw = ""
    req.on("data", c => (raw += c))
    req.on("end", () => { try { resolve(JSON.parse(raw)) } catch { resolve({}) } })
    req.on("error", () => resolve({}))
  })
}

module.exports = async function handler(req, res) {
  Object.entries(CORS).forEach(([k, v]) => res.setHeader(k, v))
  res.setHeader("Content-Type", "application/json")
  if (req.method === "OPTIONS") return res.status(204).end()

  const url    = new URL(req.url, `http://${req.headers.host}`)
  const path   = url.pathname
  const method = req.method
  const body   = await readBody(req)

  const ok  = (data, s = 200) => res.status(s).end(JSON.stringify(data))
  const bad = (msg, s = 400)  => res.status(s).end(JSON.stringify({ error: msg }))

  if (method === "GET" && path === "/") {
    return ok({ service: "musicana-party-v2", status: "ok", parties: parties.size })
  }

  if (method === "POST" && path === "/party") {
    const id     = uuidv4().replace(/-/g, "").slice(0, 12)
    const hostId = uuidv4()
    const now    = Date.now()
    parties.set(id, {
      id, hostId,
      currentSong: null,
      queue:       [],
      guestCount:  0,
      guests:      [],
      chat:        [],
      reactions:   [],
      createdAt: now, updatedAt: now, expiresAt: now + PARTY_TTL_MS,
    })
    return ok({ id, hostId })
  }

  const m1      = path.match(/^\/party\/([^/]+)$/)
  const mSong   = path.match(/^\/party\/([^/]+)\/song$/)
  const mState  = path.match(/^\/party\/([^/]+)\/state$/)
  const mQueue  = path.match(/^\/party\/([^/]+)\/queue$/)
  const mQItem  = path.match(/^\/party\/([^/]+)\/queue\/([^/]+)$/)
  const mJoin   = path.match(/^\/party\/([^/]+)\/join$/)
  const mLeave  = path.match(/^\/party\/([^/]+)\/leave$/)
  const mVote   = path.match(/^\/party\/([^/]+)\/vote$/)
  const mChat   = path.match(/^\/party\/([^/]+)\/chat$/)
  const mReact  = path.match(/^\/party\/([^/]+)\/react$/)

  if (method === "GET" && m1) {
    const p = getParty(m1[1])
    if (!p) return bad("Party not found or expired", 404)
    return ok(publicParty(p))
  }

  if (method === "GET" && mState) {
    const p = getParty(mState[1])
    if (!p) return bad("Party not found or expired", 404)
    return ok(publicParty(p))
  }

  if (method === "DELETE" && m1) {
    const p = getParty(m1[1])
    if (!p) return bad("Party not found", 404)
    if (url.searchParams.get("hostId") !== p.hostId) return bad("Invalid hostId", 403)
    parties.delete(m1[1])
    return ok({ ...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.

Wilooper and others added 30 commits February 27, 2026 11:26
[WIP] Update Musicanaz frontend to utilize improved mpyapi
…-frontend

Revert "[WIP] Update Musicanaz frontend to utilize improved mpyapi"
…ease song limit

Co-authored-by: Wilooper <198341775+Wilooper@users.noreply.github.com>
…1c2d97b2-fa8d-47f3-9c64-3767093d3af5

Replace external trending APIs with own mpyapi backend; add artist-albums endpoint
…search play

Co-authored-by: Wilooper <198341775+Wilooper@users.noreply.github.com>
…seId

Co-authored-by: Wilooper <198341775+Wilooper@users.noreply.github.com>
…-api

Fix artist "All Songs" thumbnails, replace broken trending/charts backend with Deezer, add reverse-search playback
Co-authored-by: Wilooper <198341775+Wilooper@users.noreply.github.com>
Integrate toplay API: feed listening data, expose community trending
Co-authored-by: Wilooper <198341775+Wilooper@users.noreply.github.com>
…ggregation logic

Co-authored-by: Wilooper <198341775+Wilooper@users.noreply.github.com>
Add Top Artists sections to History page and Wrapped card
Co-authored-by: Wilooper <198341775+Wilooper@users.noreply.github.com>
Copilot AI and others added 13 commits March 8, 2026 09:23
…and fix sub-page back navigation

Co-authored-by: Wilooper <198341775+Wilooper@users.noreply.github.com>
Fix back navigation: use router.back() instead of router.push("/") in back buttons
[WIP] Fix hardcoded URL in Party Mode for API requests
…tory

Fix back button navigation: sync home views with browser history, fix sub-page back actions
Co-authored-by: Wilooper <198341775+Wilooper@users.noreply.github.com>
Fix Party Mode: replace jsonblob.com with external party server
…rols, enhanced chat)

Co-authored-by: Wilooper <198341775+Wilooper@users.noreply.github.com>
…ove unused variable

Co-authored-by: Wilooper <198341775+Wilooper@users.noreply.github.com>
feat: Improve Party Mode — WebRTC real-time comms, vote-sorted queue, host controls, enhanced chat
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lyricanet Ready Ready Preview, Comment Mar 11, 2026 0:33am

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants