im-sdk.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. /**
  2. * IM 平台全端通用 Web-SDK (im-sdk.js)
  3. * 作用:统一子应用调用原生设备能力的接口,防止 WebView 直接调用引发的内存崩溃 (OOM)。
  4. * 版本:v1.0.0
  5. */
  6. (function(window, document) {
  7. // ---------------------------------------------------------
  8. // 1. 环境准备:确保注入 DCloud 官方 Webview 通信 SDK
  9. // ---------------------------------------------------------
  10. if (!window.uni || !window.uni.webView) {
  11. var script = document.createElement('script');
  12. script.type = 'text/javascript';
  13. // 动态引入 uni.webview.js
  14. script.src = 'https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js';
  15. document.head.appendChild(script);
  16. }
  17. // ---------------------------------------------------------
  18. // 2. 通信基建:回调注册与响应机制
  19. // ---------------------------------------------------------
  20. window.imBridgeCallbacks = {};
  21. /**
  22. * 供 App 壳子 (uni-app) 调用的全局方法,用于将原生执行结果塞回给 H5
  23. * @param {string} callbackId - 唯一回调标志
  24. * @param {object} data - 原生层返回的数据
  25. */
  26. window.imBridgeInvoke = function(callbackId, data) {
  27. if (window.imBridgeCallbacks[callbackId]) {
  28. // 执行业务代码中传入的 success/fail 回调
  29. window.imBridgeCallbacks[callbackId](data);
  30. // 执行完毕,销毁内存中的回调函数
  31. delete window.imBridgeCallbacks[callbackId];
  32. }
  33. };
  34. /**
  35. * 内部核心方法:向 App 壳子发送指令
  36. */
  37. function sendCommandToApp(action, params, callback) {
  38. var callbackId = 'cb_' + action + '_' + Date.now() + '_' + Math.floor(Math.random() * 1000);
  39. if (typeof callback === 'function') {
  40. window.imBridgeCallbacks[callbackId] = callback;
  41. }
  42. // 确保 uni.webView 已经加载完毕再发送
  43. var checkAndSend = function() {
  44. if (window.uni && window.uni.webView) {
  45. window.uni.webView.postMessage({
  46. data: Object.assign({ action: action, callbackId: callbackId }, params)
  47. });
  48. } else {
  49. setTimeout(checkAndSend, 100); // 轮询等待 SDK 加载
  50. }
  51. };
  52. checkAndSend();
  53. }
  54. /**
  55. * 是否在 UniApp WebView 内(用于走 uni 原生拍照/相册/录像,避免 H5 直接调 input file)
  56. * 优先级:注入标记 __IN_UNIAPP__ > UA 含 uni-app > 已存在 uni.webView
  57. */
  58. function isUniAppIm() {
  59. if (window.__IN_UNIAPP__ === true) return true;
  60. if (/uni-app/i.test(navigator.userAgent || '')) return true;
  61. if (window.uni && window.uni.webView) return true;
  62. return false;
  63. }
  64. // ---------------------------------------------------------
  65. // 3. 平台标准 API (供子应用开发者主动调用)
  66. // ---------------------------------------------------------
  67. window.imSDK = {
  68. /** @returns {boolean} */
  69. isUniAppIm: isUniAppIm,
  70. /**
  71. * 调起原生相机拍照或选择相册 (返回本地路径)
  72. */
  73. chooseImage: function(options) {
  74. options = options || {};
  75. sendCommandToApp('chooseImage', {
  76. count: options.count || 1,
  77. sourceType: options.sourceType || ['camera', 'album']
  78. }, options.success);
  79. },
  80. /**
  81. * 调起原生相机录像或选择视频 (返回本地路径)
  82. */
  83. chooseVideo: function(options) {
  84. options = options || {};
  85. sendCommandToApp('chooseVideo', {
  86. sourceType: options.sourceType || ['camera', 'album'],
  87. maxDuration: options.maxDuration || 60,
  88. camera: options.camera || 'back'
  89. }, options.success);
  90. },
  91. /**
  92. * 选择系统文件 (返回本地路径)
  93. */
  94. chooseFile: function(options) {
  95. options = options || {};
  96. sendCommandToApp('chooseFile', {
  97. count: options.count || 1,
  98. extension: options.extension || []
  99. }, options.success);
  100. },
  101. /**
  102. * 【核心】将本地路径委托给原生 App 进行上传,防止大文件导致 H5 内存溢出
  103. */
  104. uploadFile: function(options) {
  105. options = options || {};
  106. if (!options.url || !options.filePath) {
  107. console.error('[imSDK] uploadFile 缺少必填参数: url 或 filePath');
  108. return;
  109. }
  110. sendCommandToApp('uploadFile', {
  111. url: options.url,
  112. filePath: options.filePath,
  113. name: options.name || 'file',
  114. formData: options.formData || {},
  115. header: options.header || {}
  116. }, options.success);
  117. }
  118. };
  119. // ---------------------------------------------------------
  120. // 4. 终极防线:全局 DOM 劫持 (保护老旧/不规范的 H5 页面)
  121. // ---------------------------------------------------------
  122. document.addEventListener('click', function(event) {
  123. var target = event.target;
  124. // 锁定目标:点击了 <input type="file">
  125. if (target.tagName && target.tagName.toUpperCase() === 'INPUT' && target.type === 'file') {
  126. // 在 UniApp WebView 内则拦截,避免系统 file 选择器;普通浏览器放行
  127. if (isUniAppIm()) {
  128. // 【核心防御】拦截浏览器默认调起系统相册/相机的行为
  129. event.preventDefault();
  130. event.stopPropagation();
  131. console.log('[imSDK] 已拦截危险的原生 input 调用,正转交底层处理...');
  132. // 判断 input 是想选图片还是视频
  133. var isVideo = target.accept && target.accept.indexOf('video') !== -1;
  134. var action = isVideo ? 'chooseVideo' : 'chooseImage';
  135. var sourceType = target.hasAttribute('capture') ? ['camera'] : ['camera', 'album'];
  136. // 强制转为调用我们的标准 API
  137. window.imSDK[action]({
  138. sourceType: sourceType,
  139. success: function(res) {
  140. // 注意:这里拿到的是 App 传回的本地路径 (tempFilePath)。
  141. // 如果是老旧 HTML 页面,强行重写 DOM 属性可能受限于浏览器跨域安全策略。
  142. // 这里派发一个自定义事件,供高级开发者捕获;普通业务建议直接改用 imSDK.chooseImage。
  143. console.log('[imSDK] 劫持执行完毕,获取到原生路径:', res);
  144. var customEvent = new CustomEvent('im-file-choosed', { detail: res });
  145. target.dispatchEvent(customEvent);
  146. }
  147. });
  148. }
  149. }
  150. }, true); // true: 捕获阶段拦截,最高优先级
  151. })(window, document);