msr-mod/scripts/prebuild-safari.js
Astrian Zheng 1b4a8ed3b4
Some checks failed
构建扩展程序 / 发布至 Chrome 应用商店 (push) Blocked by required conditions
构建扩展程序 / 发布至 Firefox 附加组件库 (push) Blocked by required conditions
构建扩展程序 / 发布至 Edge 附加组件商店 (push) Blocked by required conditions
构建扩展程序 / 构建 Chrome 扩展程序 (push) Failing after 10m58s
构建扩展程序 / 构建 Firefox 附加组件 (push) Failing after 10m50s
feat: add Safari extension support with auto-redirect functionality
- Add Safari-specific prebuild script to handle background page requirements
- Create background.html wrapper for Safari's background page architecture
- Fix TypeScript compilation errors in browserDetection.ts and PlayListItem.vue
- Add preference-aware auto-redirect via content script messaging
- Support cross-platform extension building with npm run build:safari

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-04 23:20:48 +10:00

360 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 处理 manifest.json for Safari
function processManifest() {
const manifestPath = path.join(__dirname, '../public/manifest.json');
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
// 移除本地调试相关的配置
if (manifest.host_permissions) {
manifest.host_permissions = manifest.host_permissions.filter(
permission => !permission.includes('localhost')
);
}
if (manifest.content_security_policy && manifest.content_security_policy.extension_pages) {
// 移除 CSP 中的本地开发相关配置
manifest.content_security_policy.extension_pages = manifest.content_security_policy.extension_pages
.replace(/script-src 'self' http:\/\/localhost:5173;\s*/g, '')
.replace(/\s*http:\/\/localhost:5173\s*/g, ' ')
.replace(/\s*ws:\/\/localhost:5173\s*/g, ' ')
.replace(/;\s+/g, '; ') // 标准化分号后的空格
.replace(/\s+/g, ' ') // 合并多个空格为一个
.trim();
}
// Safari 特殊处理:添加 appShell.html 到 content scripts 匹配
if (manifest.content_scripts && manifest.content_scripts[0]) {
// 添加 appShell.html 的匹配规则
const existingMatches = manifest.content_scripts[0].matches;
if (!existingMatches.includes("https://monster-siren.hypergryph.com/")) {
existingMatches.push("https://monster-siren.hypergryph.com/");
}
}
// Safari 特殊处理:使用 background.page 而不是 service_worker
if (manifest.background && manifest.background.service_worker) {
// Safari 扩展在 Manifest V3 中必须使用 persistent: false
// 但为了调试,我们暂时设为 true 来确保页面加载
manifest.background = {
page: "background.html",
persistent: true
};
}
// 创建 background.html 文件用于 Safari
const backgroundHtmlPath = path.join(__dirname, '../public/background.html');
const backgroundHtmlContent = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>MSR Mod Background</title>
</head>
<body>
<h1>MSR Mod Background Page</h1>
<p>If you can see this page, the background page is loaded!</p>
<div id="log"></div>
<script>
// 创建日志函数,同时显示在页面和控制台
function log(message) {
console.log(message);
const logDiv = document.getElementById('log');
if (logDiv) {
logDiv.innerHTML += '<div>' + message + '</div>';
}
}
log('=== SAFARI BACKGROUND PAGE LOADED ===');
log('Document ready state: ' + document.readyState);
log('Location: ' + location.href);
log('Time: ' + new Date().toISOString());
// 确保在 Safari 中正确加载脚本
try {
log('Safari extension context: ' + JSON.stringify({
chrome: typeof chrome,
browser: typeof browser,
safari: typeof safari
}));
} catch (e) {
log('Error in background.html: ' + e.message);
}
// 监听事件
document.addEventListener('DOMContentLoaded', function() {
log('=== DOMContentLoaded fired ===');
});
window.addEventListener('load', function() {
log('=== Window load fired ===');
});
log('About to load background.js...');
</script>
<script src="background.js" onload="log('background.js loaded successfully')" onerror="log('Failed to load background.js')"></script>
<script>
log('=== After background.js script tag ===');
</script>
</body>
</html>`;
fs.writeFileSync(backgroundHtmlPath, backgroundHtmlContent);
// 创建 Safari 兼容的 background.js
const backgroundJsPath = path.join(__dirname, '../public/background.js');
let backgroundJsContent = fs.readFileSync(backgroundJsPath, 'utf8');
// 检查是否已经添加过 Safari 代码,避免重复
if (backgroundJsContent.includes('=== Safari background.js starting ===')) {
console.log('Safari background.js already processed, skipping...');
} else {
// 在开头添加 Safari 调试信息(只添加一次)
const safariDebugCode = `
console.log("=== Safari background.js starting ===");
console.log("Available APIs:", {
chrome: typeof chrome,
browser: typeof browser,
safari: typeof safari
});
// Safari 特殊处理
if (typeof chrome === 'undefined' && typeof browser === 'undefined') {
console.log("No extension APIs available in Safari");
// 如果没有扩展 API创建一个空的对象避免错误
window.chrome = {
webRequest: { onBeforeRequest: { addListener: () => {} } },
storage: { sync: { get: () => Promise.resolve({}) } },
tabs: { create: () => {}, remove: () => {}, update: () => {} },
runtime: {
getURL: (path) => path,
onMessage: { addListener: () => {} }
}
};
}
// Safari 消息监听器:处理来自 content script 的重定向请求
if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.onMessage) {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('Background received message:', message);
if (message.action === 'redirect_to_extension') {
console.log('Processing redirect request from content script');
try {
// 创建新标签页并打开扩展
const extensionUrl = chrome.runtime.getURL('index.html');
chrome.tabs.create({ url: extensionUrl }, (newTab) => {
console.log('New extension tab created:', newTab.id);
// 关闭原始标签页
if (sender.tab && sender.tab.id) {
chrome.tabs.remove(sender.tab.id);
}
sendResponse({ success: true, url: extensionUrl });
});
} catch (error) {
console.error('Failed to redirect:', error);
sendResponse({ success: false, error: error.message });
}
return true; // 保持消息通道开放
}
});
}
`;
// 替换 Safari 的重定向 URL 监听
backgroundJsContent = backgroundJsContent.replace(
/{ urls: \['https:\/\/monster-siren\.hypergryph\.com\/api\/fontset', 'https:\/\/monster-siren\.hypergryph\.com\/manifest\.json'\] }/g,
"{ urls: ['https://monster-siren.hypergryph.com/api/fontset', 'https://monster-siren.hypergryph.com/manifest.json', 'https://monster-siren.hypergryph.com/'] }"
);
// 替换 Safari 的重定向判断逻辑
backgroundJsContent = backgroundJsContent.replace(
/details\.url === 'https:\/\/monster-siren\.hypergryph\.com\/manifest\.json'/g,
"(details.url === 'https://monster-siren.hypergryph.com/manifest.json' || details.url === 'https://monster-siren.hypergryph.com/')"
);
// 清理可能的重复条件
backgroundJsContent = backgroundJsContent.replace(
/\(\(details\.url === 'https:\/\/monster-siren\.hypergryph\.com\/manifest\.json' \|\| details\.url === 'https:\/\/monster-siren\.hypergryph\.com\/'\) \|\| details\.url === 'https:\/\/monster-siren\.hypergryph\.com\/'\)/g,
"(details.url === 'https://monster-siren.hypergryph.com/manifest.json' || details.url === 'https://monster-siren.hypergryph.com/')"
);
backgroundJsContent = safariDebugCode + backgroundJsContent;
}
fs.writeFileSync(backgroundJsPath, backgroundJsContent);
console.log('✅ Safari-compatible background.js created');
// 创建 Safari 专用的 content.js
const contentJsPath = path.join(__dirname, '../public/content.js');
// 检查是否已经处理过 content.js
const existingContentJs = fs.existsSync(contentJsPath) ? fs.readFileSync(contentJsPath, 'utf8') : '';
if (existingContentJs.includes('checkRedirectPreference')) {
console.log('Safari content.js already processed, skipping...');
} else {
const contentJsContent = `
// Safari 扩展 content script for redirect
console.log('MSR Mod content script loaded on:', window.location.href);
// 兼容 Safari 的浏览器 API
const browserAPI = typeof browser !== 'undefined' ? browser : chrome;
// 异步函数:检查重定向偏好设置
async function checkRedirectPreference() {
try {
console.log('Checking redirect preferences...');
// 读取偏好设置
const pref = await browserAPI.storage.sync.get('preferences');
console.log('Retrieved preferences:', pref);
// 检查自动重定向设置(默认为 true
const shouldRedirect = pref === undefined ||
pref.preferences === undefined ||
pref.preferences.autoRedirect === undefined ||
pref.preferences.autoRedirect === true;
console.log('Should redirect:', shouldRedirect);
return shouldRedirect;
} catch (error) {
console.error('Error reading preferences:', error);
// 如果读取偏好设置失败,默认重定向
return true;
}
}
// 执行重定向的函数
function performRedirect() {
console.log('Performing redirect to extension...');
try {
// 对于 Safari我们需要使用消息传递来请求重定向
// 因为 content script 无法直接访问 chrome.runtime.getURL
// 方案1尝试通过消息传递
if (typeof chrome !== 'undefined' && chrome.runtime) {
chrome.runtime.sendMessage({action: 'redirect_to_extension'}, (response) => {
if (chrome.runtime.lastError) {
console.log('Message sending failed, trying direct redirect...');
// 方案2尝试直接重定向可能在某些情况下有效
window.location.href = 'safari-web-extension://[extension-id]/index.html';
}
});
} else {
console.log('Chrome runtime not available, trying alternative redirect...');
// 方案3显示提示让用户手动打开扩展
document.body.innerHTML = \`
<div style="
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #1a1a1a;
color: white;
padding: 20px;
border-radius: 8px;
text-align: center;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
z-index: 10000;
">
<h2>MSR Mod Extension Detected</h2>
<p>Please click the MSR Mod extension icon in your Safari toolbar to open the app.</p>
<button onclick="window.close()" style="
background: #007AFF;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
">Close Tab</button>
</div>
\`;
}
} catch (error) {
console.error('Redirect failed:', error);
}
}
// 主逻辑:检查页面并根据偏好设置决定是否重定向
async function main() {
// 检查是否是目标页面
if (window.location.pathname === '/' || window.location.href.includes('appShell.html')) {
console.log('Detected target page, checking preferences...');
// 检查偏好设置
const shouldRedirect = await checkRedirectPreference();
if (shouldRedirect) {
console.log('Auto-redirect is enabled, proceeding with redirect...');
performRedirect();
} else {
console.log('Auto-redirect is disabled, skipping redirect.');
}
}
}
// 执行主逻辑
main().catch(error => {
console.error('Error in main function:', error);
});
`;
fs.writeFileSync(contentJsPath, contentJsContent);
}
console.log('✅ Safari-compatible content.js created');
// Safari 可能需要额外的权限
if (!manifest.permissions.includes('activeTab')) {
manifest.permissions.push('activeTab');
}
// 添加 Safari 特有配置
manifest.browser_specific_settings = {
safari: {
minimum_version: "14.0"
}
};
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
console.log('✅ Safari Manifest.json processed');
console.log('✅ Background.html created for Safari');
}
// 处理 index.html
function processIndexHtml() {
const indexPath = path.join(__dirname, '../index.html');
let content = fs.readFileSync(indexPath, 'utf8');
// 替换脚本地址
content = content.replace(
/src="[^"]*\/src\/main\.ts"/g,
'src="./src/main.ts"'
);
// 移除 crossorigin 属性
content = content.replace(/\s+crossorigin/g, '');
fs.writeFileSync(indexPath, content);
console.log('✅ Index.html processed for Safari');
}
// 执行处理
try {
processManifest();
processIndexHtml();
console.log('🎉 Safari build preparation completed!');
} catch (error) {
console.error('❌ Error during Safari build preparation:', error);
process.exit(1);
}