mirror of
https://github.com/ancsemi/Haven
synced 2026-04-21 13:37:41 +00:00
v2.3.5: externalize donor list, fix password redirect loop, fix plugin loader scope
This commit is contained in:
parent
674fc26671
commit
0f74fd6ec3
11 changed files with 107 additions and 49 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -11,6 +11,18 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). Haven uses [Sema
|
|||
|
||||
---
|
||||
|
||||
## [2.3.5] — 2026-02-26
|
||||
|
||||
### Added
|
||||
- **Donor list externalized** — sponsors and donors are now loaded from `donors.json` at the server root, so the list can be updated without editing HTML. The Thank You modal fetches `/api/donors` on open.
|
||||
|
||||
### Fixed
|
||||
- **Password change redirect loop** — changing your password no longer kicks your own session into an infinite redirect. The server now sends the fresh token before disconnecting sockets, and the client guards against self-eviction during password changes.
|
||||
- **Plugin loader scope** — the plugin loader now passes `globalThis` into the plugin sandbox as `_win`, so plugins can register classes that the loader can discover. Previously `new Function()` ran in a strict scope where `window` was inaccessible, breaking all plugins including the built-in MessageTimestamps.
|
||||
- **MessageTimestamps plugin** — updated to register via `_win` so it loads correctly with the fixed plugin loader.
|
||||
|
||||
---
|
||||
|
||||
## [2.3.4] — 2026-02-26
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -810,13 +810,13 @@
|
|||
<span class="discord-feat">🖥️ Windows & Linux</span>
|
||||
</div>
|
||||
<div style="margin-top: 28px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.0.3/Haven-Setup-1.0.3.exe" class="btn btn-primary" style="padding: 12px 24px; font-size: 1rem;">
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.0.4/Haven-Setup-1.0.4.exe" class="btn btn-primary" style="padding: 12px 24px; font-size: 1rem;">
|
||||
<span class="icon">⬇</span> Windows Installer
|
||||
</a>
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.0.3/Haven-1.0.3.AppImage" class="btn btn-primary" style="padding: 12px 24px; font-size: 1rem;">
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.0.4/Haven-1.0.4.AppImage" class="btn btn-primary" style="padding: 12px 24px; font-size: 1rem;">
|
||||
<span class="icon">⬇</span> Linux AppImage
|
||||
</a>
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.0.3/haven-desktop_1.0.3_amd64.deb" class="btn btn-primary" style="padding: 12px 24px; font-size: 1rem;">
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.0.4/haven-desktop_1.0.4_amd64.deb" class="btn btn-primary" style="padding: 12px 24px; font-size: 1rem;">
|
||||
<span class="icon">⬇</span> Linux .deb
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -1246,12 +1246,12 @@
|
|||
</div>
|
||||
|
||||
<div class="download-card fade-in">
|
||||
<h2>⬡ Haven Server — v2.3.4</h2>
|
||||
<h2>⬡ Haven Server — v2.3.5</h2>
|
||||
<p class="download-version">Latest stable release · Windows, macOS & Linux · ~5 MB</p>
|
||||
|
||||
<div class="download-btn-group">
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.3.4.zip" class="btn btn-primary download-main">
|
||||
<span class="icon">⬇</span> Download v2.3.4 (.zip)
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.3.5.zip" class="btn btn-primary download-main">
|
||||
<span class="icon">⬇</span> Download v2.3.5 (.zip)
|
||||
</a>
|
||||
<div class="download-alt-links">
|
||||
<a href="https://github.com/ancsemi/Haven" target="_blank">⛭ View on GitHub</a>
|
||||
|
|
@ -1268,7 +1268,11 @@
|
|||
<div class="version-list">
|
||||
<div class="version-list-inner">
|
||||
<div class="version-item">
|
||||
<div><span class="v-name">v2.3.4</span><span class="v-tag latest">Latest</span></div>
|
||||
<div><span class="v-name">v2.3.5</span><span class="v-tag latest">Latest</span></div>
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.3.5.zip">Download →</a>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<div><span class="v-name">v2.3.4</span> — Right-click voice users, donor tier styling</div>
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.3.4.zip">Download →</a>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
|
|
@ -1407,12 +1411,12 @@
|
|||
<!-- Desktop App Download Card -->
|
||||
<div class="download-card fade-in" style="margin-top: 32px; border-color: rgba(250, 166, 26, 0.25);">
|
||||
<div style="position: absolute; top: 0; left: 0; right: 0; height: 2px; background: linear-gradient(90deg, var(--accent-dim), var(--warning), var(--green)); box-shadow: 0 0 12px rgba(250, 166, 26, 0.3);"></div>
|
||||
<h2>🖥️ Haven Desktop <span class="beta-badge-inline">BETA</span> — v1.0.3</h2>
|
||||
<h2>🖥️ Haven Desktop <span class="beta-badge-inline">BETA</span> — v1.0.4</h2>
|
||||
<p class="download-version">Latest beta release · Windows & Linux · Standalone installer</p>
|
||||
|
||||
<div class="download-btn-group">
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/latest" class="btn btn-primary download-main" style="background: var(--warning); box-shadow: 0 4px 24px rgba(250, 166, 26, 0.25), 0 0 0 1px rgba(250, 166, 26, 0.3);">
|
||||
<span class="icon">⬇</span> Download Desktop v1.0.3
|
||||
<span class="icon">⬇</span> Download Desktop v1.0.4
|
||||
</a>
|
||||
<div class="download-alt-links">
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop" target="_blank">⛭ View on GitHub</a>
|
||||
|
|
|
|||
11
donors.json
Normal file
11
donors.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"sponsors": [
|
||||
"BillyAlt",
|
||||
"nexitem"
|
||||
],
|
||||
"donors": [
|
||||
"wreckedcarzz",
|
||||
"JollyOrc",
|
||||
"ArtyDaSmarty"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "haven",
|
||||
"version": "2.3.4",
|
||||
"version": "2.3.5",
|
||||
"description": "Self-hosted private chat — your server, your rules",
|
||||
"license": "MIT-NC",
|
||||
"main": "server.js",
|
||||
|
|
|
|||
|
|
@ -56,3 +56,6 @@ class MessageTimestamps {
|
|||
return `${days}d ago`;
|
||||
}
|
||||
}
|
||||
|
||||
// Register with the plugin loader's _win scope
|
||||
if (typeof _win !== 'undefined') _win.MessageTimestamps = MessageTimestamps;
|
||||
|
|
|
|||
|
|
@ -1950,24 +1950,27 @@
|
|||
<h2 class="donors-title">Thank You</h2>
|
||||
<p class="donors-subtitle">Haven is built with love and kept alive by the generosity of people like you. Every donation — big or small — means the world.</p>
|
||||
|
||||
<div class="donors-tiers">
|
||||
<div class="donors-tiers" id="donors-tiers">
|
||||
<div class="donors-tier">
|
||||
<h3 class="donors-tier-title donors-sponsor-title">✨ Sponsors</h3>
|
||||
<div class="donors-grid">
|
||||
<span class="donor-chip donor-sponsor">BillyAlt</span>
|
||||
</div>
|
||||
<div class="donors-grid" id="sponsors-grid"></div>
|
||||
</div>
|
||||
|
||||
<div class="donors-tier">
|
||||
<h3 class="donors-tier-title donors-donor-title">💛 Donors</h3>
|
||||
<div class="donors-grid">
|
||||
<span class="donor-chip">wreckedcarzz</span>
|
||||
<span class="donor-chip">JollyOrc</span>
|
||||
<span class="donor-chip">nexitem</span>
|
||||
<span class="donor-chip">ArtyDaSmarty</span>
|
||||
</div>
|
||||
<div class="donors-grid" id="donors-grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
fetch('/api/donors').then(r=>r.json()).then(d=>{
|
||||
const sg=document.getElementById('sponsors-grid');
|
||||
const dg=document.getElementById('donors-grid');
|
||||
(d.sponsors||[]).forEach(n=>{ const s=document.createElement('span'); s.className='donor-chip donor-sponsor'; s.textContent=n; sg.appendChild(s); });
|
||||
(d.donors||[]).forEach(n=>{ const s=document.createElement('span'); s.className='donor-chip'; s.textContent=n; dg.appendChild(s); });
|
||||
}).catch(()=>{});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<p class="donors-note">New donors will be added each release. If you've donated and don't see your name, it'll be in the next update!</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -367,6 +367,8 @@ class HavenApp {
|
|||
});
|
||||
|
||||
this.socket.on('connect_error', (err) => {
|
||||
// Don't kick during password change — socket will reconnect with fresh token
|
||||
if (this._justChangedPassword) return;
|
||||
if (err.message === 'Invalid token' || err.message === 'Authentication required' || err.message === 'Session expired') {
|
||||
localStorage.removeItem('haven_token');
|
||||
localStorage.removeItem('haven_user');
|
||||
|
|
@ -380,15 +382,11 @@ class HavenApp {
|
|||
// Password was changed on this or another session — force re-login
|
||||
this.socket.on('force-logout', (data) => {
|
||||
if (data && data.reason === 'password_changed') {
|
||||
// If WE just changed the password, we already have the fresh token
|
||||
const freshToken = localStorage.getItem('haven_token');
|
||||
if (freshToken && freshToken !== this.token) {
|
||||
// Another tab/device changed it but we somehow got a new token — use it
|
||||
this.token = freshToken;
|
||||
this.socket.auth.token = freshToken;
|
||||
// If WE just changed the password, skip the kick — we already have the fresh token
|
||||
if (this._justChangedPassword) {
|
||||
this._justChangedPassword = false;
|
||||
return;
|
||||
}
|
||||
// Otherwise this is a different session — kick to login
|
||||
localStorage.removeItem('haven_token');
|
||||
localStorage.removeItem('haven_user');
|
||||
window.location.href = '/';
|
||||
|
|
@ -2348,6 +2346,9 @@ class HavenApp {
|
|||
if (np.length < 8) return hint.textContent = 'New password must be 8+ characters';
|
||||
if (np !== conf) return hint.textContent = 'Passwords do not match';
|
||||
|
||||
// Flag to prevent force-logout from kicking us out
|
||||
this._justChangedPassword = true;
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/auth/change-password', {
|
||||
method: 'POST',
|
||||
|
|
@ -2385,7 +2386,10 @@ class HavenApp {
|
|||
document.getElementById('current-password').value = '';
|
||||
document.getElementById('new-password').value = '';
|
||||
document.getElementById('confirm-password').value = '';
|
||||
// Clear the flag after a delay so socket reconnects go through
|
||||
setTimeout(() => { this._justChangedPassword = false; }, 5000);
|
||||
} catch {
|
||||
this._justChangedPassword = false;
|
||||
hint.textContent = 'Network error';
|
||||
hint.classList.add('error');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,11 +184,12 @@ window.HavenPluginLoader = (function () {
|
|||
const code = await resp.text();
|
||||
|
||||
// Execute in a Function scope so plugins can define classes
|
||||
const factory = new Function('HavenApi', code + '\n;return (typeof module !== "undefined" && module.exports) || (typeof exports !== "undefined" ? exports : null);');
|
||||
const exported = factory(HavenApi);
|
||||
// Pass globalThis as _win so plugins can register classes via _win.ClassName = ...
|
||||
const factory = new Function('HavenApi', '_win', code + '\n;return (typeof module !== "undefined" && module.exports) || (typeof exports !== "undefined" ? exports : null);');
|
||||
const exported = factory(HavenApi, globalThis);
|
||||
|
||||
// The plugin should place its class on window, or we find the last class defined
|
||||
// Convention: plugin sets module.exports = ClassName or window.PluginName = class { ... }
|
||||
// Convention: plugin sets module.exports = ClassName or _win.PluginName = class { ... }
|
||||
// We'll look for any new class on window that has start()/stop()
|
||||
let PluginClass = null;
|
||||
|
||||
|
|
@ -201,10 +202,10 @@ window.HavenPluginLoader = (function () {
|
|||
PluginClass = window[baseName];
|
||||
} else {
|
||||
// Fallback: look for any class defined via the code — we wrap it
|
||||
// The code itself may call window.XYZ = class { ... }
|
||||
// The code itself may call _win.XYZ = class { ... }
|
||||
// Just re-execute looking for the return value
|
||||
const fn2 = new Function('HavenApi', code + '\n;return typeof start === "function" ? { start, stop: typeof stop === "function" ? stop : () => {} } : null;');
|
||||
const obj = fn2(HavenApi);
|
||||
const fn2 = new Function('HavenApi', '_win', code + '\n;return typeof start === "function" ? { start, stop: typeof stop === "function" ? stop : () => {} } : null;');
|
||||
const obj = fn2(HavenApi, globalThis);
|
||||
if (obj) PluginClass = function() { this.start = obj.start; this.stop = obj.stop || (() => {}); };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
server.js
11
server.js
|
|
@ -462,6 +462,17 @@ app.get('/games/flappy', (req, res) => {
|
|||
res.sendFile(path.join(__dirname, 'public', 'games', 'flappy.html'));
|
||||
});
|
||||
|
||||
// ── Donors / sponsors list (loaded from donors.json) ──
|
||||
app.get('/api/donors', (req, res) => {
|
||||
try {
|
||||
const donorsPath = path.join(__dirname, 'donors.json');
|
||||
const data = JSON.parse(fs.readFileSync(donorsPath, 'utf-8'));
|
||||
res.json(data);
|
||||
} catch {
|
||||
res.json({ sponsors: [], donors: [] });
|
||||
}
|
||||
});
|
||||
|
||||
// ── Health check (CORS allowed for multi-server status pings) ──
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.set('Access-Control-Allow-Origin', '*');
|
||||
|
|
|
|||
19
src/auth.js
19
src/auth.js
|
|
@ -263,18 +263,23 @@ router.post('/change-password', async (req, res) => {
|
|||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
// Send the response FIRST so the client can store the fresh token
|
||||
// before we disconnect sockets (prevents redirect loop)
|
||||
res.json({ message: 'Password changed successfully', token: freshToken });
|
||||
|
||||
// Disconnect all existing sockets for this user (forces re-login on other sessions)
|
||||
const io = req.app.get('io');
|
||||
if (io) {
|
||||
for (const [, s] of io.sockets.sockets) {
|
||||
if (s.user && s.user.id === user.id) {
|
||||
s.emit('force-logout', { reason: 'password_changed' });
|
||||
s.disconnect(true);
|
||||
// Small delay to let the HTTP response reach the client first
|
||||
setTimeout(() => {
|
||||
for (const [, s] of io.sockets.sockets) {
|
||||
if (s.user && s.user.id === user.id) {
|
||||
s.emit('force-logout', { reason: 'password_changed' });
|
||||
s.disconnect(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
res.json({ message: 'Password changed successfully', token: freshToken });
|
||||
} catch (err) {
|
||||
console.error('Change password error:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
|
|
|
|||
|
|
@ -810,13 +810,13 @@
|
|||
<span class="discord-feat">🖥️ Windows & Linux</span>
|
||||
</div>
|
||||
<div style="margin-top: 28px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.0.3/Haven-Setup-1.0.3.exe" class="btn btn-primary" style="padding: 12px 24px; font-size: 1rem;">
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.0.4/Haven-Setup-1.0.4.exe" class="btn btn-primary" style="padding: 12px 24px; font-size: 1rem;">
|
||||
<span class="icon">⬇</span> Windows Installer
|
||||
</a>
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.0.3/Haven-1.0.3.AppImage" class="btn btn-primary" style="padding: 12px 24px; font-size: 1rem;">
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.0.4/Haven-1.0.4.AppImage" class="btn btn-primary" style="padding: 12px 24px; font-size: 1rem;">
|
||||
<span class="icon">⬇</span> Linux AppImage
|
||||
</a>
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.0.3/haven-desktop_1.0.3_amd64.deb" class="btn btn-primary" style="padding: 12px 24px; font-size: 1rem;">
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.0.4/haven-desktop_1.0.4_amd64.deb" class="btn btn-primary" style="padding: 12px 24px; font-size: 1rem;">
|
||||
<span class="icon">⬇</span> Linux .deb
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -1246,12 +1246,12 @@
|
|||
</div>
|
||||
|
||||
<div class="download-card fade-in">
|
||||
<h2>⬡ Haven Server — v2.3.4</h2>
|
||||
<h2>⬡ Haven Server — v2.3.5</h2>
|
||||
<p class="download-version">Latest stable release · Windows, macOS & Linux · ~5 MB</p>
|
||||
|
||||
<div class="download-btn-group">
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.3.4.zip" class="btn btn-primary download-main">
|
||||
<span class="icon">⬇</span> Download v2.3.4 (.zip)
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.3.5.zip" class="btn btn-primary download-main">
|
||||
<span class="icon">⬇</span> Download v2.3.5 (.zip)
|
||||
</a>
|
||||
<div class="download-alt-links">
|
||||
<a href="https://github.com/ancsemi/Haven" target="_blank">⛭ View on GitHub</a>
|
||||
|
|
@ -1268,7 +1268,11 @@
|
|||
<div class="version-list">
|
||||
<div class="version-list-inner">
|
||||
<div class="version-item">
|
||||
<div><span class="v-name">v2.3.4</span><span class="v-tag latest">Latest</span></div>
|
||||
<div><span class="v-name">v2.3.5</span><span class="v-tag latest">Latest</span></div>
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.3.5.zip">Download →</a>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<div><span class="v-name">v2.3.4</span> — Right-click voice users, donor tier styling</div>
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.3.4.zip">Download →</a>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
|
|
@ -1407,12 +1411,12 @@
|
|||
<!-- Desktop App Download Card -->
|
||||
<div class="download-card fade-in" style="margin-top: 32px; border-color: rgba(250, 166, 26, 0.25);">
|
||||
<div style="position: absolute; top: 0; left: 0; right: 0; height: 2px; background: linear-gradient(90deg, var(--accent-dim), var(--warning), var(--green)); box-shadow: 0 0 12px rgba(250, 166, 26, 0.3);"></div>
|
||||
<h2>🖥️ Haven Desktop <span class="beta-badge-inline">BETA</span> — v1.0.3</h2>
|
||||
<h2>🖥️ Haven Desktop <span class="beta-badge-inline">BETA</span> — v1.0.4</h2>
|
||||
<p class="download-version">Latest beta release · Windows & Linux · Standalone installer</p>
|
||||
|
||||
<div class="download-btn-group">
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop/releases/latest" class="btn btn-primary download-main" style="background: var(--warning); box-shadow: 0 4px 24px rgba(250, 166, 26, 0.25), 0 0 0 1px rgba(250, 166, 26, 0.3);">
|
||||
<span class="icon">⬇</span> Download Desktop v1.0.3
|
||||
<span class="icon">⬇</span> Download Desktop v1.0.4
|
||||
</a>
|
||||
<div class="download-alt-links">
|
||||
<a href="https://github.com/ancsemi/Haven-Desktop" target="_blank">⛭ View on GitHub</a>
|
||||
|
|
|
|||
Loading…
Reference in a new issue