fix: hardening pass — permissions, socket reconnect, canvas setup, arrangements

- Fix 403 on save for public collection viewers (return role in GET board response)
- Add read-only status indicator (StatusBar + StatusIndicator)
- Fix beforeunload save to use fetch+keepalive with auth header
- Socket reconnect now rejoins board room automatically
- Canvas setup uses polling instead of brittle 200ms timer
- Fix double user:left on disconnect (use disconnecting event, snapshot rooms)
- Thread + comment creation wrapped in db.transaction
- Prevent owner downgrade via addCollectionMember (check existing member)
- Bound redirect depth in downloadImage to 5
- Arrangement operations anchor to bounding box top-left (no drift)
- Distribute H/V also anchor to top-left
- Fix annotations fetch to use axios api instance (401 interceptor)
- Replace require() with static import in shortcut-definitions
This commit is contained in:
Hiren Kangad
2026-03-11 08:08:21 +05:30
parent fc2d9df741
commit 6518ed6763
13 changed files with 141 additions and 84 deletions
+6 -3
View File
@@ -178,15 +178,18 @@ router.post('/boards/:boardId/images', upload.single('image'), async (req, res)
/**
* Download an image from a URL. Returns { buffer, mimeType, filename }.
*/
function downloadImage(imageUrl) {
function downloadImage(imageUrl, maxRedirects = 5) {
return new Promise((resolve, reject) => {
const parsed = new URL(imageUrl);
const client = parsed.protocol === 'https:' ? https : http;
client.get(imageUrl, { timeout: 30000 }, (response) => {
// Follow redirects (up to 5)
// Follow redirects up to maxRedirects times
if ([301, 302, 303, 307, 308].includes(response.statusCode) && response.headers.location) {
return downloadImage(response.headers.location).then(resolve).catch(reject);
if (maxRedirects <= 0) {
return reject(new Error('Too many redirects'));
}
return downloadImage(response.headers.location, maxRedirects - 1).then(resolve).catch(reject);
}
if (response.statusCode !== 200) {