PageSourceSearch

https://vizsurge.com/static/join-party.js?v=1

js vizsurge.com collected 2026-09-24 14:19:51 UTC 7,063 bytes, 200 lines download raw bytes

1/* ══════════════════════════════════════════════════════════════════
2   join-party.js -- shared "Join Party" widget logic for instant.html
3   and watch.html. A dedicated file rather than adding this to croom.js
4   because instant.html doesn't load croom.js at all, and croom.js is
5   large enough (11k+ lines, global room-page state, its own
6   DOMContentLoaded assumptions) that pulling it into instant.html just
7   for this would be a much bigger, riskier change than one small,
8   self-contained script both pages can load identically.
9
10   Expects these element IDs to exist on the page (see the Join Party
11   section HTML on each page):
12     #join-party-link       - the pasted-link text input
13     #join-party-btn        - the initial "Join" button
14     #join-party-code-row   - wrapper for the code step, hidden until needed
15     #join-party-code-input - the access-code text input
16     #join-party-code-btn   - the code step's "Join" button
17     #join-party-error      - inline error message element
18
19   Reuses the exact same backend validation the browser already relies
20   on for private rooms (POST /api/crooms/<id>/access) rather than
21   reimplementing any access logic here -- this file only parses the
22   pasted link and decides which existing endpoint to call.
23   ══════════════════════════════════════════════════════════════════ */
24(function () {
25  "use strict";
26
27  let _pendingRoomId = null;
28
29  function _parseRoomRef(raw) {
30    const input = (raw || "").trim();
31    if (!input) return null;
32
33    let embeddedCode = null;
34    const codeMatch = input.match(/[?&]code=([^&\s]+)/i);
35    if (codeMatch) embeddedCode = decodeURIComponent(codeMatch[1]);
36
37    // Pretty URL: /watch/<slug> or /room/<slug> -- the format real share
38    // links actually use whenever the room has a slug at all.
39    const prettyMatch = input.match(/\/(?:watch|room)\/([a-zA-Z0-9_-]+)/i);
40    if (prettyMatch) {
41      return { type: "slug", value: prettyMatch[1].toLowerCase(), embeddedCode };
42    }
43
44    // Fallback format: crooms-chat.html?room=<id> -- used when a room
45    // has no slug.
46    const idMatch = input.match(/[?&]room=(\d+)/i);
47    if (idMatch) {
48      return { type: "id", value: parseInt(idMatch[1], 10), embeddedCode };
49    }
50
51    return null;
52  }
53
54  function _showError(msg) {
55    const err = document.getElementById("join-party-error");
56    if (err) {
57      err.textContent = msg;
58      err.style.display = "block";
59    }
60  }
61
62  function _clearError() {
63    const err = document.getElementById("join-party-error");
64    if (err) {
65      err.style.display = "none";
66      err.textContent = "";
67    }
68  }
69
70  function _navigateToRoom(room) {
71    const from = room.room_category === "watch" ? "&from=watch" : "";
72    window.location.href = "/crooms-chat.html?room=" + room.id + from;
73  }
74
75  async function _fetchRoom(ref) {
76    const url =
77      ref.type === "slug"
78        ? "/api/crooms/slug/" + encodeURIComponent(ref.value)
79        : "/api/crooms/" + ref.value + "/preview";
80    const res = await fetch(url);
81    return res.json();
82  }
83
84  async function _submitCode(roomId, code) {
85    const res = await fetch("/api/crooms/" + roomId + "/access", {
86      method: "POST",
87      headers: { "Content-Type": "application/json" },
88      body: JSON.stringify({ code }),
89    });
90    return res.json();
91  }
92
93  function _revealCodeStep() {
94    const codeRow = document.getElementById("join-party-code-row");
95    if (codeRow) codeRow.style.display = "flex";
96    const codeInput = document.getElementById("join-party-code-input");
97    if (codeInput) codeInput.focus();
98  }
99
100  async function _handleJoin() {
101    _clearError();
102    const linkInput = document.getElementById("join-party-link");
103    const ref = _parseRoomRef(linkInput && linkInput.value);
104    if (!ref) {
105      _showError("That doesn't look like a valid party link.");
106      return;
107    }
108
109    const btn = document.getElementById("join-party-btn");
110    if (btn) btn.disabled = true;
111
112    try {
113      const data = await _fetchRoom(ref);
114      if (!data.ok || !data.room) {
115        _showError("This party couldn't be found. It may have ended.");
116        return;
117      }
118      const room = data.room;
119
120      if (room.room_type !== "private") {
121        _navigateToRoom(room);
122        return;
123      }
124
125      _pendingRoomId = room.id;
126
127      // A link copied via Share already carries the code for rooms the
128      // sharer owns -- if it's present and still valid, join immediately
129      // rather than making the person retype what the link already gave.
130      if (ref.embeddedCode) {
131        const accessResult = await _submitCode(room.id, ref.embeddedCode);
132        if (accessResult.ok) {
133          _navigateToRoom(room);
134          return;
135        }
136        // Embedded code was wrong or stale (e.g. regenerated since the
137        // link was shared) -- fall through to the manual prompt below
138        // rather than dead-ending here.
139      }
140
141      _revealCodeStep();
142    } catch (err) {
143      _showError("Something went wrong. Please try again.");
144    } finally {
145      if (btn) btn.disabled = false;
146    }
147  }
148
149  async function _handleCodeSubmit() {
150    _clearError();
151    if (!_pendingRoomId) return;
152
153    const codeInput = document.getElementById("join-party-code-input");
154    const code = ((codeInput && codeInput.value) || "").trim().toUpperCase();
155    if (!code) {
156      _showError("Please enter the party code.");
157      return;
158    }
159
160    const btn = document.getElementById("join-party-code-btn");
161    if (btn) btn.disabled = true;
162
163    try {
164      const result = await _submitCode(_pendingRoomId, code);
165      if (result.ok) {
166        window.location.href = "/crooms-chat.html?room=" + _pendingRoomId;
167        return;
168      }
169      if (result.error === "blocked") {
170        _showError("You don't have access to this party.");
171      } else {
172        _showError("That code doesn't match. Please check and try again.");
173      }
174    } catch (err) {
175      _showError("Something went wrong. Please try again.");
176    } finally {
177      if (btn) btn.disabled = false;
178    }
179  }
180
181  document.addEventListener("DOMContentLoaded", function () {
182    const linkInput = document.getElementById("join-party-link");
183    const btn = document.getElementById("join-party-btn");
184    const codeInput = document.getElementById("join-party-code-input");
185    const codeBtn = document.getElementById("join-party-code-btn");
186
187    if (btn) btn.addEventListener("click", _handleJoin);
188    if (linkInput) {
189      linkInput.addEventListener("keydown", function (e) {
190        if (e.key === "Enter") _handleJoin();
191      });
192    }
193    if (codeBtn) codeBtn.addEventListener("click", _handleCodeSubmit);
194    if (codeInput) {
195      codeInput.addEventListener("keydown", function (e) {
196        if (e.key === "Enter") _handleCodeSubmit();
197      });
198    }
199  });
200})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.