a54210b0d1
新增共享 HostBridge 公开主站 origin 和默认 URL 常量 移动壳启动 URL、分享和 WebView 策略改为引用共享主站来源 桌面壳配置检查反查 Rust WEB_APP_ORIGIN 与共享契约一致 同步原生壳方案和共享决策记录
221 lines
5.3 KiB
TypeScript
221 lines
5.3 KiB
TypeScript
import { DEFAULT_MOBILE_SHELL_WEB_URL } from './url';
|
|
|
|
const MOBILE_WEBVIEW_BLOCKED_DOWNLOAD_PROTOCOLS = new Set([
|
|
'blob:',
|
|
'data:',
|
|
'file:',
|
|
'filesystem:',
|
|
]);
|
|
|
|
export type MobileWebViewNavigationRequest = {
|
|
url?: string | null;
|
|
};
|
|
|
|
export function shouldBlockMobileWebViewDownloadUrl(
|
|
rawUrl: string | null | undefined,
|
|
) {
|
|
if (!rawUrl) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
return MOBILE_WEBVIEW_BLOCKED_DOWNLOAD_PROTOCOLS.has(
|
|
new URL(rawUrl, DEFAULT_MOBILE_SHELL_WEB_URL).protocol,
|
|
);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function shouldBlockMobileWebViewNavigationRequest(
|
|
request: MobileWebViewNavigationRequest,
|
|
) {
|
|
return shouldBlockMobileWebViewDownloadUrl(request.url);
|
|
}
|
|
|
|
export const BLOCK_WEBVIEW_DOWNLOAD_SCRIPT = `
|
|
(function() {
|
|
var blockedDownloadProtocols = {
|
|
'blob:': true,
|
|
'data:': true,
|
|
'file:': true,
|
|
'filesystem:': true
|
|
};
|
|
|
|
function shouldBlockDownloadUrl(rawUrl) {
|
|
if (typeof rawUrl !== 'string') {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
return blockedDownloadProtocols[new URL(rawUrl, window.location.href).protocol] === true;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function findDownloadAnchor(target) {
|
|
if (!target) {
|
|
return null;
|
|
}
|
|
|
|
if (typeof target.closest === 'function') {
|
|
return target.closest('a');
|
|
}
|
|
|
|
var element = target;
|
|
while (element && element !== document) {
|
|
if (element.tagName === 'A') {
|
|
return element;
|
|
}
|
|
element = element.parentNode;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function shouldBlockAnchor(anchor) {
|
|
return Boolean(
|
|
anchor &&
|
|
(anchor.hasAttribute('download') || shouldBlockDownloadUrl(anchor.href))
|
|
);
|
|
}
|
|
|
|
function blockDownloadEvent(event) {
|
|
event.preventDefault();
|
|
event.stopImmediatePropagation();
|
|
event.stopPropagation();
|
|
return false;
|
|
}
|
|
|
|
document.addEventListener('click', function(event) {
|
|
if (shouldBlockAnchor(findDownloadAnchor(event.target))) {
|
|
return blockDownloadEvent(event);
|
|
}
|
|
}, true);
|
|
|
|
var originalOpen = window.open;
|
|
window.open = function(url) {
|
|
if (shouldBlockDownloadUrl(url)) {
|
|
return null;
|
|
}
|
|
|
|
return originalOpen.apply(window, arguments);
|
|
};
|
|
|
|
if (window.HTMLAnchorElement && HTMLAnchorElement.prototype.click) {
|
|
var originalAnchorClick = HTMLAnchorElement.prototype.click;
|
|
HTMLAnchorElement.prototype.click = function() {
|
|
if (shouldBlockAnchor(this)) {
|
|
return undefined;
|
|
}
|
|
|
|
return originalAnchorClick.apply(this, arguments);
|
|
};
|
|
}
|
|
})();
|
|
true;
|
|
`;
|
|
|
|
export const TRACK_MOBILE_WEBVIEW_HISTORY_SCRIPT = `
|
|
(function() {
|
|
if (window.__GENARRATIVE_MOBILE_HISTORY_TRACKER_INSTALLED__) {
|
|
if (typeof window.__GENARRATIVE_MOBILE_POST_NAVIGATION_STATE__ === 'function') {
|
|
window.__GENARRATIVE_MOBILE_POST_NAVIGATION_STATE__();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
var historyIndexKey = '__genarrativeMobileHistoryIndex';
|
|
var originalPushState = window.history.pushState.bind(window.history);
|
|
var originalReplaceState = window.history.replaceState.bind(window.history);
|
|
|
|
function readIndex(state) {
|
|
if (!state || typeof state !== 'object') {
|
|
return null;
|
|
}
|
|
|
|
var value = state[historyIndexKey];
|
|
return Number.isInteger(value) && value >= 0 ? value : null;
|
|
}
|
|
|
|
function stateWithIndex(state, index) {
|
|
if (state && typeof state === 'object' && !Array.isArray(state)) {
|
|
var nextState = {};
|
|
Object.keys(state).forEach(function(key) {
|
|
nextState[key] = state[key];
|
|
});
|
|
nextState[historyIndexKey] = index;
|
|
return nextState;
|
|
}
|
|
|
|
var indexedState = {};
|
|
indexedState[historyIndexKey] = index;
|
|
return indexedState;
|
|
}
|
|
|
|
var currentIndex = readIndex(window.history.state) || 0;
|
|
|
|
function postNavigationState() {
|
|
var bridge = window.ReactNativeWebView;
|
|
if (!bridge || typeof bridge.postMessage !== 'function') {
|
|
return;
|
|
}
|
|
|
|
bridge.postMessage(JSON.stringify({
|
|
type: 'genarrative.mobile.historyState',
|
|
canGoBack: currentIndex > 0
|
|
}));
|
|
}
|
|
|
|
function replaceCurrentState() {
|
|
try {
|
|
originalReplaceState(
|
|
stateWithIndex(window.history.state, currentIndex),
|
|
'',
|
|
window.location.href
|
|
);
|
|
} catch (error) {}
|
|
}
|
|
|
|
window.history.pushState = function(state, title, url) {
|
|
var nextIndex = currentIndex + 1;
|
|
var result = originalPushState(stateWithIndex(state, nextIndex), title, url);
|
|
currentIndex = nextIndex;
|
|
postNavigationState();
|
|
return result;
|
|
};
|
|
|
|
window.history.replaceState = function(state, title, url) {
|
|
var result = originalReplaceState(
|
|
stateWithIndex(state, currentIndex),
|
|
title,
|
|
url
|
|
);
|
|
postNavigationState();
|
|
return result;
|
|
};
|
|
|
|
window.__GENARRATIVE_MOBILE_POST_NAVIGATION_STATE__ = postNavigationState;
|
|
window.addEventListener('popstate', function(event) {
|
|
var nextIndex = readIndex(event.state);
|
|
currentIndex = nextIndex === null ? 0 : nextIndex;
|
|
if (nextIndex === null) {
|
|
replaceCurrentState();
|
|
}
|
|
postNavigationState();
|
|
});
|
|
|
|
window.__GENARRATIVE_MOBILE_HISTORY_TRACKER_INSTALLED__ = true;
|
|
replaceCurrentState();
|
|
postNavigationState();
|
|
})();
|
|
true;
|
|
`;
|
|
|
|
export const MOBILE_WEBVIEW_BEFORE_CONTENT_SCRIPT = `
|
|
${BLOCK_WEBVIEW_DOWNLOAD_SCRIPT}
|
|
${TRACK_MOBILE_WEBVIEW_HISTORY_SCRIPT}
|
|
true;
|
|
`;
|