mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
Fix blocked website access and add troubleshooting page
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { cwd } from 'node:process';
|
||||
import {
|
||||
HOST_ACCESS_REQUIRED_STATUS,
|
||||
addTabHostAccessRequest,
|
||||
describeTabUrl,
|
||||
inspectTabHostAccess,
|
||||
normalizeTabId,
|
||||
removeTabHostAccessRequest
|
||||
} from '../extension/host-access.js';
|
||||
|
||||
assert.equal(HOST_ACCESS_REQUIRED_STATUS, 'host_permission_required');
|
||||
assert.equal(normalizeTabId(null), null);
|
||||
assert.equal(normalizeTabId(undefined), null);
|
||||
assert.equal(normalizeTabId(''), null);
|
||||
assert.equal(normalizeTabId(0), null);
|
||||
assert.equal(normalizeTabId('42'), 42);
|
||||
assert.deepEqual(describeTabUrl('https://emby.example:8443/web/index.html'), {
|
||||
url: 'https://emby.example:8443/web/index.html',
|
||||
host: 'emby.example:8443',
|
||||
originPattern: 'https://emby.example:8443/*'
|
||||
});
|
||||
assert.deepEqual(describeTabUrl('http://localhost:8096/web/'), {
|
||||
url: 'http://localhost:8096/web/',
|
||||
host: 'localhost:8096',
|
||||
originPattern: 'http://localhost:8096/*'
|
||||
});
|
||||
assert.equal(describeTabUrl('chrome://extensions/'), null);
|
||||
assert.equal(describeTabUrl('not a url'), null);
|
||||
|
||||
let containsRequest = null;
|
||||
const deniedChrome = {
|
||||
tabs: {
|
||||
get: async tabId => ({ id: tabId, url: 'https://video.example/watch' })
|
||||
},
|
||||
permissions: {
|
||||
contains: async request => {
|
||||
containsRequest = request;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
const access = await inspectTabHostAccess(deniedChrome, 42);
|
||||
assert.equal(access.granted, false);
|
||||
assert.equal(access.host, 'video.example');
|
||||
assert.deepEqual(containsRequest, { origins: ['https://video.example/*'] });
|
||||
|
||||
let requestedTabId = null;
|
||||
const requestChrome = {
|
||||
permissions: {
|
||||
addHostAccessRequest: async ({ tabId }) => { requestedTabId = tabId; }
|
||||
}
|
||||
};
|
||||
assert.equal(await addTabHostAccessRequest(requestChrome, 42), true);
|
||||
assert.equal(requestedTabId, 42);
|
||||
assert.equal(await addTabHostAccessRequest({ permissions: {} }, 42), false);
|
||||
|
||||
let removedTabId = null;
|
||||
const removeRequestChrome = {
|
||||
permissions: {
|
||||
removeHostAccessRequest: async ({ tabId }) => { removedTabId = tabId; }
|
||||
}
|
||||
};
|
||||
assert.equal(await removeTabHostAccessRequest(removeRequestChrome, 42), true);
|
||||
assert.equal(removedTabId, 42);
|
||||
assert.equal(await removeTabHostAccessRequest({ permissions: {} }, 42), false);
|
||||
|
||||
const background = fs.readFileSync(path.join(cwd(), 'extension', 'background.js'), 'utf8');
|
||||
const popup = fs.readFileSync(path.join(cwd(), 'extension', 'popup.js'), 'utf8');
|
||||
const popupHtml = fs.readFileSync(path.join(cwd(), 'extension', 'popup.html'), 'utf8');
|
||||
|
||||
assert.match(background, /await activateTargetTab\(message\.tabId, message\.tabTitle\)/,
|
||||
'SET_TARGET_TAB must await successful activation before acknowledging it');
|
||||
assert.match(background, /addTabHostAccessRequest\(chrome, tabId\)/,
|
||||
'failed injection must register Chrome host-access request');
|
||||
assert.match(background, /retryPendingTarget\(\)/,
|
||||
'pending target must resume after the user grants access');
|
||||
assert.match(background, /activationGeneration !== targetActivationGeneration/,
|
||||
'stale concurrent tab activations must not overwrite the newest selection');
|
||||
const activateTargetBody = background.slice(
|
||||
background.indexOf('async function activateTargetTab'),
|
||||
background.indexOf('async function retryPendingTarget')
|
||||
);
|
||||
assert.ok(
|
||||
activateTargetBody.indexOf('await injectContentScript') < activateTargetBody.indexOf('currentTabId = selectedTabId'),
|
||||
'a tab must not become current until its content script injection succeeds'
|
||||
);
|
||||
assert.match(background, /removeTabHostAccessRequest\(chrome, pendingTabId\)/,
|
||||
'clearing a pending target must also clear Chrome toolbar access requests');
|
||||
assert.match(popup, /response\?\.status === 'host_permission_required'/,
|
||||
'popup must render the structured host-access failure');
|
||||
assert.match(popup, /chrome\.permissions\.request\(\{ origins: \[originPattern\] \}\)/,
|
||||
'retry button must request withheld host access directly');
|
||||
assert.match(popupHtml, /id="siteAccessNotice"/,
|
||||
'popup must contain a persistent site-access notice');
|
||||
|
||||
console.log('host access recovery tests passed');
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const read = relativePath => fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
|
||||
|
||||
const template = read('website/template.html');
|
||||
const foundationCss = read('website/styles/foundation.css');
|
||||
const supportCss = read('website/styles/support.css');
|
||||
const helpPage = read('website/site-access-help.html');
|
||||
const build = read('website/build.cjs');
|
||||
const localesDir = path.join(repoRoot, 'website', 'locales');
|
||||
|
||||
assert.equal((template.match(/class="site-access-banner"/g) || []).length, 1,
|
||||
'localized landing template must contain exactly one support banner');
|
||||
assert.match(template, /href="\/site-access-help\.html"/,
|
||||
'every compiled language must link to the English root help page');
|
||||
assert.match(template, /\{\{SUPPORT_BANNER_TEXT\}\}/);
|
||||
assert.match(template, /\{\{SUPPORT_BANNER_CTA\}\}/);
|
||||
assert.match(foundationCss, /\.site-access-banner\s*\{/,
|
||||
'landing bundle must style the support banner without deferred CSS');
|
||||
|
||||
for (const file of fs.readdirSync(localesDir).filter(file => file.endsWith('.json'))) {
|
||||
const locale = JSON.parse(fs.readFileSync(path.join(localesDir, file), 'utf8'));
|
||||
assert.ok(locale.SUPPORT_BANNER_TEXT?.trim(), `${file} is missing SUPPORT_BANNER_TEXT`);
|
||||
assert.ok(locale.SUPPORT_BANNER_CTA?.trim(), `${file} is missing SUPPORT_BANNER_CTA`);
|
||||
}
|
||||
|
||||
assert.match(helpPage, /<html lang="en"/);
|
||||
assert.match(helpPage, /rel="canonical" href="https:\/\/sync\.koalastuff\.net\/site-access-help\.html"/);
|
||||
assert.doesNotMatch(helpPage, /\{\{[A-Z0-9_-]+\}\}/,
|
||||
'English-only static help page must not contain unresolved template placeholders');
|
||||
assert.match(helpPage, /Access requested/,
|
||||
'help page must explain the new Chromium access-request UI');
|
||||
assert.match(helpPage, /Firefox/,
|
||||
'help page must include Firefox-specific recovery instructions');
|
||||
assert.match(helpPage, /“Peers \(1\)” only shows yourself/,
|
||||
'help page must distinguish relay peer visibility from website access');
|
||||
assert.match(helpPage, /video and audio stream never leave your browser/,
|
||||
'help page must explain the privacy boundary of the permission');
|
||||
assert.match(helpPage, /support\.google\.com\/chrome/);
|
||||
assert.match(helpPage, /support\.mozilla\.org/);
|
||||
assert.match(supportCss, /html\.theme-light \.support-card/,
|
||||
'support cards must define a readable light-theme surface');
|
||||
|
||||
assert.match(build, /\{ src: 'site-access-help\.html', dest: 'site-access-help\.html' \}/,
|
||||
'website build must copy the help page to the root output');
|
||||
assert.match(build, /'styles\/support\.css'/,
|
||||
'website build must include support page styles in style.min.css');
|
||||
assert.match(build, /https:\/\/sync\.koalastuff\.net\/site-access-help\.html/,
|
||||
'sitemap must include the English-only help page');
|
||||
|
||||
console.log('Site-access help page and localized landing banner are complete');
|
||||
@@ -20,6 +20,7 @@ const checks = [
|
||||
['content video finder', 'node', ['scripts/test-content-video-finder.cjs']],
|
||||
['audio settings', 'node', ['scripts/test-audio-settings.mjs']],
|
||||
['popup refresh cooldown', 'node', ['scripts/test-popup-refresh-cooldown.mjs']],
|
||||
['host access recovery', 'node', ['scripts/test-host-access.mjs']],
|
||||
['server syntax index', 'node', ['-c', 'server/index.js']],
|
||||
['server syntax ops', 'node', ['-c', 'server/ops.js']],
|
||||
['server syntax rate-limiter', 'node', ['-c', 'server/rate-limiter.js']],
|
||||
@@ -28,6 +29,7 @@ const checks = [
|
||||
['background syntax', 'node', ['-c', 'extension/background.js']],
|
||||
['locale coverage', 'node', ['scripts/test-locales.cjs']],
|
||||
['website locale coverage', 'node', ['scripts/test-website-locales.mjs']],
|
||||
['website site-access help', 'node', ['scripts/test-site-access-help.mjs']],
|
||||
['website theme coverage', 'node', ['scripts/test-website-theme.mjs']],
|
||||
['lint', 'npm', ['run', 'lint']],
|
||||
['root production audit', 'npm', ['audit', '--omit=dev']],
|
||||
|
||||
Reference in New Issue
Block a user