im-sdk.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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. // 3. 平台标准 API (供子应用开发者主动调用)
  56. // ---------------------------------------------------------
  57. window.imSDK = {
  58. /**
  59. * 调起原生相机拍照或选择相册 (返回本地路径)
  60. */
  61. chooseImage: function(options) {
  62. options = options || {};
  63. sendCommandToApp('chooseImage', {
  64. count: options.count || 1,
  65. sourceType: options.sourceType || ['camera', 'album']
  66. }, options.success);
  67. },
  68. /**
  69. * 调起原生相机录像或选择视频 (返回本地路径)
  70. */
  71. chooseVideo: function(options) {
  72. options = options || {};
  73. sendCommandToApp('chooseVideo', {
  74. sourceType: options.sourceType || ['camera', 'album'],
  75. maxDuration: options.maxDuration || 60,
  76. camera: options.camera || 'back'
  77. }, options.success);
  78. },
  79. /**
  80. * 选择系统文件 (返回本地路径)
  81. */
  82. chooseFile: function(options) {
  83. options = options || {};
  84. sendCommandToApp('chooseFile', {
  85. count: options.count || 1,
  86. extension: options.extension || []
  87. }, options.success);
  88. },
  89. /**
  90. * 【核心】将本地路径委托给原生 App 进行上传,防止大文件导致 H5 内存溢出
  91. */
  92. uploadFile: function(options) {
  93. options = options || {};
  94. if (!options.url || !options.filePath) {
  95. console.error('[imSDK] uploadFile 缺少必填参数: url 或 filePath');
  96. return;
  97. }
  98. sendCommandToApp('uploadFile', {
  99. url: options.url,
  100. filePath: options.filePath,
  101. name: options.name || 'file',
  102. formData: options.formData || {},
  103. header: options.header || {}
  104. }, options.success);
  105. }
  106. };
  107. // ---------------------------------------------------------
  108. // 4. 终极防线:全局 DOM 劫持 (保护老旧/不规范的 H5 页面)
  109. // ---------------------------------------------------------
  110. document.addEventListener('click', function(event) {
  111. var target = event.target;
  112. // 锁定目标:点击了 <input type="file">
  113. if (target.tagName && target.tagName.toUpperCase() === 'INPUT' && target.type === 'file') {
  114. // 粗略判断是否在 App 内 (如果不在,则放行,走普通浏览器逻辑)
  115. // 建议:在你的 App 壳子 webview 加载时,给 userAgent 拼上一个自定义标识,如 'MyIMPlatform'
  116. var isInsideApp = /uni-app/i.test(navigator.userAgent);
  117. if (isInsideApp) {
  118. // 【核心防御】拦截浏览器默认调起系统相册/相机的行为
  119. event.preventDefault();
  120. event.stopPropagation();
  121. console.log('[imSDK] 已拦截危险的原生 input 调用,正转交底层处理...');
  122. // 判断 input 是想选图片还是视频
  123. var isVideo = target.accept && target.accept.indexOf('video') !== -1;
  124. var action = isVideo ? 'chooseVideo' : 'chooseImage';
  125. var sourceType = target.hasAttribute('capture') ? ['camera'] : ['camera', 'album'];
  126. // 强制转为调用我们的标准 API
  127. window.imSDK[action]({
  128. sourceType: sourceType,
  129. success: function(res) {
  130. // 注意:这里拿到的是 App 传回的本地路径 (tempFilePath)。
  131. // 如果是老旧 HTML 页面,强行重写 DOM 属性可能受限于浏览器跨域安全策略。
  132. // 这里派发一个自定义事件,供高级开发者捕获;普通业务建议直接改用 imSDK.chooseImage。
  133. console.log('[imSDK] 劫持执行完毕,获取到原生路径:', res);
  134. var customEvent = new CustomEvent('im-file-choosed', { detail: res });
  135. target.dispatchEvent(customEvent);
  136. }
  137. });
  138. }
  139. }
  140. }, true); // true: 捕获阶段拦截,最高优先级
  141. })(window, document);