UNPKG

aile-capacitor-aiff

Version:

AIFF WebView plugin for Capacitor with iframe-based web implementation

286 lines (283 loc) 9.78 kB
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; } } }