/** * IM 平台全端通用 Web-SDK (im-sdk.js) * 作用:统一子应用调用原生设备能力的接口,防止 WebView 直接调用引发的内存崩溃 (OOM)。 * 版本:v1.0.0 */ (function(window, document) { // --------------------------------------------------------- // 1. 环境准备:确保注入 DCloud 官方 Webview 通信 SDK // --------------------------------------------------------- if (!window.uni || !window.uni.webView) { var script = document.createElement('script'); script.type = 'text/javascript'; // 动态引入 uni.webview.js script.src = 'https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js'; document.head.appendChild(script); } // --------------------------------------------------------- // 2. 通信基建:回调注册与响应机制 // --------------------------------------------------------- window.imBridgeCallbacks = {}; /** * 供 App 壳子 (uni-app) 调用的全局方法,用于将原生执行结果塞回给 H5 * @param {string} callbackId - 唯一回调标志 * @param {object} data - 原生层返回的数据 */ window.imBridgeInvoke = function(callbackId, data) { if (window.imBridgeCallbacks[callbackId]) { // 执行业务代码中传入的 success/fail 回调 window.imBridgeCallbacks[callbackId](data); // 执行完毕,销毁内存中的回调函数 delete window.imBridgeCallbacks[callbackId]; } }; /** * 内部核心方法:向 App 壳子发送指令 */ function sendCommandToApp(action, params, callback) { var callbackId = 'cb_' + action + '_' + Date.now() + '_' + Math.floor(Math.random() * 1000); if (typeof callback === 'function') { window.imBridgeCallbacks[callbackId] = callback; } // 确保 uni.webView 已经加载完毕再发送 var checkAndSend = function() { if (window.uni && window.uni.webView) { window.uni.webView.postMessage({ data: Object.assign({ action: action, callbackId: callbackId }, params) }); } else { setTimeout(checkAndSend, 100); // 轮询等待 SDK 加载 } }; checkAndSend(); } // --------------------------------------------------------- // 3. 平台标准 API (供子应用开发者主动调用) // --------------------------------------------------------- window.imSDK = { /** * 调起原生相机拍照或选择相册 (返回本地路径) */ chooseImage: function(options) { options = options || {}; sendCommandToApp('chooseImage', { count: options.count || 1, sourceType: options.sourceType || ['camera', 'album'] }, options.success); }, /** * 调起原生相机录像或选择视频 (返回本地路径) */ chooseVideo: function(options) { options = options || {}; sendCommandToApp('chooseVideo', { sourceType: options.sourceType || ['camera', 'album'], maxDuration: options.maxDuration || 60, camera: options.camera || 'back' }, options.success); }, /** * 选择系统文件 (返回本地路径) */ chooseFile: function(options) { options = options || {}; sendCommandToApp('chooseFile', { count: options.count || 1, extension: options.extension || [] }, options.success); }, /** * 【核心】将本地路径委托给原生 App 进行上传,防止大文件导致 H5 内存溢出 */ uploadFile: function(options) { options = options || {}; if (!options.url || !options.filePath) { console.error('[imSDK] uploadFile 缺少必填参数: url 或 filePath'); return; } sendCommandToApp('uploadFile', { url: options.url, filePath: options.filePath, name: options.name || 'file', formData: options.formData || {}, header: options.header || {} }, options.success); } }; // --------------------------------------------------------- // 4. 终极防线:全局 DOM 劫持 (保护老旧/不规范的 H5 页面) // --------------------------------------------------------- document.addEventListener('click', function(event) { var target = event.target; // 锁定目标:点击了 if (target.tagName && target.tagName.toUpperCase() === 'INPUT' && target.type === 'file') { // 粗略判断是否在 App 内 (如果不在,则放行,走普通浏览器逻辑) // 建议:在你的 App 壳子 webview 加载时,给 userAgent 拼上一个自定义标识,如 'MyIMPlatform' var isInsideApp = /uni-app/i.test(navigator.userAgent); if (isInsideApp) { // 【核心防御】拦截浏览器默认调起系统相册/相机的行为 event.preventDefault(); event.stopPropagation(); console.log('[imSDK] 已拦截危险的原生 input 调用,正转交底层处理...'); // 判断 input 是想选图片还是视频 var isVideo = target.accept && target.accept.indexOf('video') !== -1; var action = isVideo ? 'chooseVideo' : 'chooseImage'; var sourceType = target.hasAttribute('capture') ? ['camera'] : ['camera', 'album']; // 强制转为调用我们的标准 API window.imSDK[action]({ sourceType: sourceType, success: function(res) { // 注意:这里拿到的是 App 传回的本地路径 (tempFilePath)。 // 如果是老旧 HTML 页面,强行重写 DOM 属性可能受限于浏览器跨域安全策略。 // 这里派发一个自定义事件,供高级开发者捕获;普通业务建议直接改用 imSDK.chooseImage。 console.log('[imSDK] 劫持执行完毕,获取到原生路径:', res); var customEvent = new CustomEvent('im-file-choosed', { detail: res }); target.dispatchEvent(customEvent); } }); } } }, true); // true: 捕获阶段拦截,最高优先级 })(window, document);