Normalize shared tab titles

This commit is contained in:
KoalaDev
2026-07-02 10:20:25 +02:00
parent 0af22998c8
commit 01762b0c2f
4 changed files with 31 additions and 4 deletions
+16 -1
View File
@@ -60,6 +60,22 @@
- **Legal/moderation:** Unclear what moderation requirements would apply if users can exchange chat messages. Could be relevant depending on jurisdiction.
- **Status:** Under evaluation, may come later.
### Cross-frame video detection and control
- **Priority:** P3
- **Category:** Compatibility / Embedded Players
- **Background:** KoalaSync currently injects on demand into the selected tab's top frame. This works for normal top-frame players, including current Emby/Jellyfin usage, but does not cover cases where the real `<video>` lives inside a cross-origin iframe or an `about:blank`/`srcdoc` player frame.
- **Possible approach:** Add an opt-in frame bridge where child frames announce detected videos to the top frame, and the top frame routes remote play/pause/seek commands to the active child video.
- **Status:** Future compatibility work, not needed for current Emby behavior.
### Local extension E2E smoke tests
- **Priority:** P2
- **Category:** Testing / Release Confidence
- **Background:** The release verification covers unit tests, server integration, syntax, lint, audits, and builds, but it does not currently run a real browser extension flow. A small local E2E smoke suite would catch regressions in content-script injection, tab navigation reinjection, remote seek handling, and iframe player support.
- **Possible approach:** Add a separate local-only Playwright smoke command that loads the unpacked extension, opens two controlled video pages, and verifies play/pause/seek through the actual extension path. Keep it outside `npm run verify` until it is stable enough for CI.
- **Status:** Backlog, recommended before larger content-script or frame-bridge changes.
---
## ❌ Rejected
@@ -70,4 +86,3 @@
|---|---|
| *(none yet)* | |
+2 -2
View File
@@ -2,7 +2,7 @@ import { EVENTS, OFFICIAL_LANDING_PAGE_URL, SUPPORT_URL, getReviewUrl } from './
import { BLACKLIST_DOMAINS } from './shared/blacklist.js';
import { getAvatarForName, generateUsername, USERNAME_ADJECTIVES, USERNAME_NOUNS } from './shared/names.js';
import { loadLocale, translateDOM, getMessage, getSystemLanguage } from './i18n.js';
import { TITLE_PRIVACY_MODES, normalizeSendTabTitle } from './title-privacy.js';
import { TITLE_PRIVACY_MODES, normalizeSendTabTitle, normalizeTabTitle } from './title-privacy.js';
const elements = {
@@ -920,7 +920,7 @@ async function populateTabs(providedPeers = null, providedTargetTabId = null) {
// Smart Matching Logic — exclude own tabTitle to prevent self-match (computed once)
const cleanTitle = (rawTitle) => {
if (!rawTitle) return '';
return rawTitle
return (normalizeTabTitle(rawTitle) || '')
.replace(/(?:\s*[-\|•]\s*(?:YouTube|Twitch|Jellyfin|Emby|Netflix|Vimeo|Dailymotion).*)$/i, '')
.replace(/^(?:Netflix|Twitch|YouTube|Emby|Jellyfin)\s*[-\|•]\s*/i, '')
.trim();
+7 -1
View File
@@ -17,9 +17,15 @@ export function normalizeSendTabTitle(sendTabTitle, legacyMode = TITLE_PRIVACY_M
return normalizeTitlePrivacyMode(legacyMode) === TITLE_PRIVACY_MODES.FULL;
}
export function normalizeTabTitle(title) {
if (typeof title !== 'string') return null;
const normalized = title.replace(/^\s*(?:\(\d{1,2}\)|\[\d{1,2}\])\s+/, '').trim();
return normalized.length > 0 ? normalized : null;
}
export function sanitizeTabTitle(title, sendTabTitle) {
if (!sendTabTitle) return null;
return typeof title === 'string' && title.length > 0 ? title : null;
return normalizeTabTitle(title);
}
export function sanitizeSharedTitle(title, mode) {
+6
View File
@@ -3,6 +3,7 @@ import {
TITLE_PRIVACY_MODES,
applyTitlePrivacyToPayload,
normalizeSendTabTitle,
normalizeTabTitle,
normalizeTitlePrivacyMode,
sanitizeSharedTitle,
sanitizeTabTitle
@@ -15,8 +16,13 @@ assert.equal(normalizeSendTabTitle(undefined, TITLE_PRIVACY_MODES.FULL), true);
assert.equal(normalizeSendTabTitle(undefined, TITLE_PRIVACY_MODES.EPISODE), false);
assert.equal(normalizeSendTabTitle(true, TITLE_PRIVACY_MODES.HIDDEN), true);
assert.equal(normalizeSendTabTitle(false, TITLE_PRIVACY_MODES.FULL), false);
assert.equal(normalizeTabTitle('(12) Testvideo - YouTube'), 'Testvideo - YouTube');
assert.equal(normalizeTabTitle('[7] Testvideo - YouTube'), 'Testvideo - YouTube');
assert.equal(normalizeTabTitle('(500) Days of Summer'), '(500) Days of Summer');
assert.equal(normalizeTabTitle(' '), null);
assert.equal(sanitizeTabTitle('Private Tab', true), 'Private Tab');
assert.equal(sanitizeTabTitle('(12) Private Tab', true), 'Private Tab');
assert.equal(sanitizeTabTitle('Private Tab', false), null);
assert.equal(sanitizeTabTitle('', true), null);