aile-capacitor-aiff
Version:
AIFF WebView plugin for Capacitor with iframe-based web implementation
286 lines (283 loc) • 9.78 kB
JavaScript
export class AiffWebViewWeb {
constructor() {
this.modalElement = null;
this.iframeElement = null;
this.urlChangeListeners = [];
this.closeListeners = [];
this.messageListeners = [];
}
async openWebView(options) {
if (this.modalElement) {
this.closeWebView();
}
this.createModal(options);
this.setupEventListeners();
}
async executeScript(options) {
var _a;
if ((_a = this.iframeElement) === null || _a === void 0 ? void 0 : _a.contentWindow) {
// @ts-ignore
this.iframeElement.contentWindow.eval(options.code);
}
}
async postMessage(options) {
var _a;
if ((_a = this.iframeElement) === null || _a === void 0 ? void 0 : _a.contentWindow) {
// 使用 postMessageToJS 的方式发送消息
const script = `window.postMessageToJS(${JSON.stringify(options.detail)});`;
this.executeScript({ code: script });
}
}
async setUrl(options) {
if (this.iframeElement) {
this.iframeElement.src = options.url;
}
}
async reload() {
if (this.iframeElement) {
this.iframeElement.src = this.iframeElement.src;
}
}
async addListener(eventName, listenerFunc) {
switch (eventName) {
case 'urlChangeEvent':
this.urlChangeListeners.push(listenerFunc);
break;
case 'closeEvent':
this.closeListeners.push(listenerFunc);
break;
case 'messageFromWebview':
this.messageListeners.push(listenerFunc);
break;
case 'browserPageLoaded':
// Web platform doesn't need explicit handling
break;
case 'pageLoadError':
// Web platform doesn't need explicit handling
break;
}
return {
remove: async () => {
switch (eventName) {
case 'urlChangeEvent':
this.urlChangeListeners = this.urlChangeListeners.filter(l => l !== listenerFunc);
break;
case 'closeEvent':
this.closeListeners = this.closeListeners.filter(l => l !== listenerFunc);
break;
case 'messageFromWebview':
this.messageListeners = this.messageListeners.filter(l => l !== listenerFunc);
break;
}
}
};
}
createModal(options) {
// Create modal container
this.modalElement = document.createElement('div');
this.modalElement.className = 'aiff-webview-modal';
this.modalElement.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 300ms ease-in-out;
`;
// Create container
const container = document.createElement('div');
container.className = 'aiff-webview-container';
container.style.cssText = `
width: 90%;
height: 80%;
max-width: 1200px;
background: white;
border-radius: 8px;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
transform: scale(0.9);
transition: transform 300ms ease-in-out;
`;
// Create toolbar
const toolbar = this.createToolbar(options);
container.appendChild(toolbar);
// Create iframe
this.iframeElement = document.createElement('iframe');
this.iframeElement.src = options.url;
this.iframeElement.style.cssText = `
flex: 1;
width: 100%;
border: none;
background: white;
`;
if (options.iframeSandbox) {
this.iframeElement.sandbox.add(...options.iframeSandbox);
}
else {
this.iframeElement.sandbox.add('allow-scripts', 'allow-same-origin', 'allow-forms');
}
container.appendChild(this.iframeElement);
this.modalElement.appendChild(container);
document.body.appendChild(this.modalElement);
// Animate in
requestAnimationFrame(() => {
this.modalElement.style.opacity = '1';
container.style.transform = 'scale(1)';
});
}
createToolbar(options) {
const toolbar = document.createElement('div');
toolbar.className = 'aiff-webview-toolbar';
toolbar.style.cssText = `
height: 44px;
background: ${options.toolbarColor || '#ffffff'};
border-bottom: 1px solid #e0e0e0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
flex-shrink: 0;
`;
// Close button
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '×';
closeBtn.style.cssText = `
background: none;
border: none;
font-size: 24px;
cursor: pointer;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
`;
closeBtn.onclick = () => this.closeWebView();
// Title
const title = document.createElement('div');
title.textContent = options.title || 'WebView';
title.style.cssText = `
font-size: 16px;
font-weight: 600;
flex: 1;
text-align: center;
margin: 0 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
// Reload button (if enabled)
const reloadBtn = document.createElement('button');
reloadBtn.innerHTML = '↻';
reloadBtn.style.cssText = `
background: none;
border: none;
font-size: 16px;
cursor: pointer;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
`;
reloadBtn.onclick = () => this.reload();
toolbar.appendChild(closeBtn);
toolbar.appendChild(title);
if (options.showReloadButton) {
toolbar.appendChild(reloadBtn);
}
else {
toolbar.appendChild(document.createElement('div')); // Spacer
}
return toolbar;
}
setupEventListeners() {
if (this.iframeElement) {
this.iframeElement.addEventListener('load', () => {
var _a;
// URL change event
const url = ((_a = this.iframeElement) === null || _a === void 0 ? void 0 : _a.src) || '';
this.urlChangeListeners.forEach(listener => listener({ url }));
// Inject AIFF JavaScript interface
this.injectAiffJavaScriptInterface();
});
// Listen for messages from iframe
window.addEventListener('message', (event) => {
var _a, _b;
if (event.source === ((_a = this.iframeElement) === null || _a === void 0 ? void 0 : _a.contentWindow)) {
// 处理 AIFF 特定的消息格式
if (((_b = event.data) === null || _b === void 0 ? void 0 : _b.type) === 'aiff-mobile-app-message') {
this.messageListeners.forEach(listener => listener({
detail: event.data.payload,
rawMessage: JSON.stringify(event.data.payload)
}));
}
else {
// 兼容普通消息格式
this.messageListeners.forEach(listener => listener({
detail: event.data,
rawMessage: JSON.stringify(event.data)
}));
}
}
});
}
}
injectAiffJavaScriptInterface() {
var _a;
if (!((_a = this.iframeElement) === null || _a === void 0 ? void 0 : _a.contentWindow))
return;
const script = `
(function() {
if (!window.mobileApp) {
window.mobileApp = {
postMessage: function(message) {
if (window.parent && window.parent !== window) {
window.parent.postMessage({
type: 'aiff-mobile-app-message',
payload: message
}, '*');
}
}
};
}
// 向页面发送消息的方法
if (!window.postMessageToJS) {
window.postMessageToJS = function(detail) {
window.dispatchEvent(new CustomEvent('messageFromNative', { detail }));
};
}
// 兼容性别名
if (!window.AIFF) {
window.AIFF = {
postMessage: window.mobileApp.postMessage,
receiveMessage: function(callback) {
window.addEventListener('messageFromNative', callback);
}
};
}
})();
`;
this.executeScript({ code: script });
}
closeWebView() {
var _a;
if (this.modalElement) {
// Notify close listeners
const url = ((_a = this.iframeElement) === null || _a === void 0 ? void 0 : _a.src) || '';
this.closeListeners.forEach(listener => listener({ url }));
// Remove modal
document.body.removeChild(this.modalElement);
this.modalElement = null;
this.iframeElement = null;
}
}
}