From 2f691638bbae2e9d9224ba827fe600a988b5253c Mon Sep 17 00:00:00 2001 From: kamilic Date: Sun, 26 Dec 2021 16:03:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E9=83=A8=E5=88=86?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=B9=B6=E5=A2=9E=E5=8A=A0=20sendBeacon=20?= =?UTF-8?q?=E7=9A=84=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KKJSBridge/TS/dist/KKJSBridgeAJAXHook.js | 371 +++++++++++++++++- .../TS/dist/KKJSBridgeAJAXProtocolHook.js | 166 +++++--- .../TS/src/hook/KKJSBridgeAjaxProtocolHook.ts | 131 ++++--- .../TS/src/hook/KKJSBridgeSendBeaconHook.ts | 33 ++ KKJSBridge/TS/src/index.ts | 4 + KKJSBridge/TS/src/indexold.ts | 4 + 6 files changed, 596 insertions(+), 113 deletions(-) create mode 100644 KKJSBridge/TS/src/hook/KKJSBridgeSendBeaconHook.ts diff --git a/KKJSBridge/TS/dist/KKJSBridgeAJAXHook.js b/KKJSBridge/TS/dist/KKJSBridgeAJAXHook.js index 577d125..07f8f44 100644 --- a/KKJSBridge/TS/dist/KKJSBridgeAJAXHook.js +++ b/KKJSBridge/TS/dist/KKJSBridgeAJAXHook.js @@ -1,8 +1,8 @@ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define('lib/fetch.js', factory) : - (global = global || self, global.KKJSBridge = factory()); -}(this, (function () { 'use strict'; + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.KKJSBridge = factory()); +})(this, (function () { 'use strict'; var support = { searchParams: 'URLSearchParams' in self, @@ -580,29 +580,41 @@ } /*! ***************************************************************************** - Copyright (c) Microsoft Corporation. All rights reserved. - Licensed under the Apache License, Version 2.0 (the "License"); you may not use - this file except in compliance with the License. You may obtain a copy of the - License at http://www.apache.org/licenses/LICENSE-2.0 + Copyright (c) Microsoft Corporation. - THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED - WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, - MERCHANTABLITY OR NON-INFRINGEMENT. + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. - See the Apache Version 2.0 License for specific language governing permissions - and limitations under the License. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ + var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + function __values(o) { - var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; + var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); - return { + if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; + throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); } var _KKJSBridgeFormData = /** @class */ (function () { @@ -1067,7 +1079,7 @@ /** * AJAX 相关方法 */ - var _KKJSBridgeXHR = /** @class */ (function () { + var _KKJSBridgeXHR$1 = /** @class */ (function () { function _KKJSBridgeXHR() { } // 静态属性和方法 @@ -1476,6 +1488,327 @@ return _KKJSBridgeXHR; }()); + /** + * AJAX 相关方法 + */ + var _KKJSBridgeXHR = /** @class */ (function () { + function _KKJSBridgeXHR() { + } + /** + * 统一处理 body 内容 + */ + _KKJSBridgeXHR.resolveRequestBody = function (body) { + return new Promise(function (res) { + if (!body) { + res({}); + } + else if (body instanceof ArrayBuffer) { + res({ + bodyType: 'ArrayBuffer', + // 说明是 ArrayBuffer,转成 base64 + value: KKJSBridgeUtil.convertArrayBufferToBase64(body) + }); + } + else if (body instanceof Blob) { + var bodyType_1 = 'Blob'; + var fileReader = new FileReader(); + fileReader.onload = function (ev) { + // 说明是 Blob,转成 base64 + var base64 = ev.target.result; + res({ + value: base64, + bodyType: bodyType_1 + }); + }; + fileReader.readAsDataURL(body); + } + else if (body instanceof FormData) { + // 说明是表单 + KKJSBridgeUtil.convertFormDataToJson(body, function (json) { + res({ + bodyType: 'FormData', + formEnctype: 'multipart/form-data', + value: json + }); + }); + } + else if (body instanceof URLSearchParams) { + res({ + bodyType: 'String', + formEnctype: 'application/x-www-form-urlencoded', + value: body.toString() + }); + } + else { + // 说明是字符串或者json + res({ + bodyType: 'String', + value: body + }); + } + }); + }; + // 静态属性和方法 + _KKJSBridgeXHR.moduleName = 'ajax'; + _KKJSBridgeXHR.globalId = Math.floor(Math.random() * 100000); + _KKJSBridgeXHR.callbackCache = []; + /** + * 生成 ajax 请求唯一id + */ + _KKJSBridgeXHR.generateXHRRequestId = function () { + return (new Date).getTime() + "" + _KKJSBridgeXHR.globalId++; // 时间戳 + 当前上下文唯一id,生成请求id + }; + /** + * 给表单生成新的 action + */ + _KKJSBridgeXHR.generateNewActionForForm = function (form, requestId) { + var orignAction = form.action; + form.action = _KKJSBridgeXHR.generateNewUrlWithRequestId(orignAction, requestId); + }; + /** + * 利用 requestId 生成新的 url + */ + _KKJSBridgeXHR.generateNewUrlWithRequestId = function (url, requestId) { + var orignAction = url; + // 通过 a 标签来辅助拼接新的 action + var aTag = document.createElement("a"); + aTag.href = orignAction; + var search = aTag.search ? aTag.search : ""; + var hash = aTag.hash ? aTag.hash : ""; + if (/KKJSBridge-RequestId/.test(orignAction)) { // 防止重复追加 requestId + aTag.search = aTag.search.replace(/KKJSBridge-RequestId=(\d+)/, "KKJSBridge-RequestId=" + requestId); + } + else if (aTag.search && aTag.search.length > 0) { + var s = aTag.search; + if (/KKJSBridge-RequestId/.test(s)) { // 防止重复追加 requestId + aTag.search = s.replace(/KKJSBridge-RequestId=(\d+)/, "KKJSBridge-RequestId=" + requestId); + } + else { + aTag.search = s + "&KKJSBridge-RequestId=" + requestId; + } + } + else { + aTag.search = "?KKJSBridge-RequestId=" + requestId; + } + url = orignAction.replace(search, "").replace(hash, ""); + if ("#" === url.trim()) { + url = ""; + } + var newAction = url + aTag.search + aTag.hash; + return newAction; + }; + /** + * 给 open url 生成带请求 id 的新 url + */ + _KKJSBridgeXHR.generateNewOpenUrlWithRequestId = function (url, requestId) { + var getOpenUrlReuestId = function (requestId) { + return "^^^^" + requestId + "^^^^"; + }; + var openUrlReuestReg = /\^\^\^\^(\d+)\^\^\^\^/; + // 通过 a 标签来辅助拼接新的 action + var aTag = document.createElement("a"); + aTag.href = url; + var hash = aTag.hash ? aTag.hash : ""; + if (openUrlReuestReg.test(aTag.hash)) { + aTag.hash = aTag.hash.replace(openUrlReuestReg, getOpenUrlReuestId(requestId)); + } + else if (aTag.hash && aTag.hash.length > 0) { + aTag.hash = aTag.hash + getOpenUrlReuestId(requestId); + } + else { + aTag.hash = getOpenUrlReuestId(requestId); + } + url = url.replace(hash, ""); + if ("#" === url.trim()) { + url = ""; + } + var newUrl = url + aTag.hash; + return newUrl; + }; + /** + * 是否是非正常的 http 请求。比如 url: blob:https:// 场景下,去发送 XMLHTTPRequest,会导致请求失败 + */ + _KKJSBridgeXHR.isNonNormalHttpRequest = function (url, httpMethod) { + var pattern = /^((http|https):\/\/)/; + var isNonNormalRequest = !pattern.test(url) && httpMethod === "GET"; + return isNonNormalRequest; + }; + /** + * 发送 body 到 native 侧缓存起来 + * @param xhr + * @param originMethod + * @param originArguments + * @param body + */ + _KKJSBridgeXHR.sendBodyToNativeForCache = function (targetType, target, originMethod, originArguments, request, requestAsync) { + /* + ajax 同步请求只支持纯文本数据,不支持 Blob 和 FormData 数据。 + 如果要支持的话,必须使用 FileReaderSync 对象,但是该对象只在 workers 里可用, + 因为在主线程里进行同步 I/O 操作可能会阻塞用户界面。 + https://developer.mozilla.org/zh-CN/docs/Web/API/FileReaderSync + */ + if (requestAsync === void 0) { requestAsync = true; } + var requestId = target.requestId; + var cacheCallback = { + requestId: requestId, + callback: function () { + // if (targetType === "AJAX") {// ajax + // // 发送之前设置自定义请求头,好让 native 拦截并从缓存里获取 body + // target.setRequestHeader("KKJSBridge-RequestId", requestId); + // } + if (targetType === "FORM") { // 表单 submit + // 发送之前修改 action,让 action 带上 requestId + _KKJSBridgeXHR.generateNewActionForForm(target, requestId); + } + // 调用原始 send 方法 + return originMethod.apply(target, originArguments); + } + }; + if (requestAsync) { // 异步请求 + // 缓存 callbcak + _KKJSBridgeXHR.callbackCache[requestId] = cacheCallback; + // 发送 body 请求到 native + window.KKJSBridge.call(_KKJSBridgeXHR.moduleName, 'cacheAJAXBody', request, function (message) { + // 处理 native 侧缓存完毕后的消息 + var callbackFromNative = message; + var requestId = callbackFromNative.requestId; + // 通过请求 id,找到原始 send 方法并调用 + if (_KKJSBridgeXHR.callbackCache[requestId]) { + var callbackFromNative_1 = _KKJSBridgeXHR.callbackCache[requestId]; + if (callbackFromNative_1 && callbackFromNative_1.callback && typeof callbackFromNative_1.callback == "function") { + callbackFromNative_1.callback(); + } + delete _KKJSBridgeXHR.callbackCache[requestId]; + } + }); + return; + } + // 同步请求 + // 发送 body 请求到 native + window.KKJSBridge.syncCall(_KKJSBridgeXHR.moduleName, 'cacheAJAXBody', request); + // 发送完成后继续请求原始 send 方法 + cacheCallback.callback(); + }; + /** + * 安装 AJAX Proxy + */ + _KKJSBridgeXHR.setupHook = function () { + /** + * 只 hook open/send 方法 + */ + var originOpen = XMLHttpRequest.prototype.open; + XMLHttpRequest.prototype.open = function (method, url, async, username, password) { + var args = [].slice.call(arguments); + var xhr = this; + // 生成唯一请求id + xhr.requestId = _KKJSBridgeXHR.generateXHRRequestId(); + xhr.requestUrl = url; + xhr.requestHref = document.location.href; + xhr.requestMethod = method; + xhr.requestAsync = async; + if (_KKJSBridgeXHR.isNonNormalHttpRequest(url, method)) { // 如果是非正常请求,则调用原始 open + return originOpen.apply(xhr, args); + } + if (!window.KKJSBridgeConfig.ajaxHook) { // 如果没有开启 ajax hook,则调用原始 open + return originOpen.apply(xhr, args); + } + // 生成新的 url + args[1] = _KKJSBridgeXHR.generateNewUrlWithRequestId(url, xhr.requestId); + originOpen.apply(xhr, args); + }; + var originSend = XMLHttpRequest.prototype.send; + XMLHttpRequest.prototype.send = function (body) { + var args = [].slice.call(arguments); + var xhr = this; + var requestRaw = { + requestId: xhr.requestId, + requestHref: xhr.requestHref, + requestUrl: xhr.requestUrl, + bodyType: "String", + value: null + }; + if (_KKJSBridgeXHR.isNonNormalHttpRequest(xhr.requestUrl, xhr.requestMethod)) { // 如果是非正常请求,则调用原始 send + return originSend.apply(xhr, args); + } + if (!window.KKJSBridgeConfig.ajaxHook) { // 如果没有开启 ajax hook,则调用原始 send + return originSend.apply(xhr, args); + } + if (!body || body instanceof Document) { + // 没有 body 或 body 是 Document,调用原始 send + return originSend.apply(xhr, args); + } + else { + _KKJSBridgeXHR.resolveRequestBody(body).then(function (req) { + var request = __assign(__assign({}, requestRaw), req); + // 发送到 native 缓存起来 + _KKJSBridgeXHR.sendBodyToNativeForCache('AJAX', xhr, originSend, args, request); + }); + } + }; + /** + * hook form submit 方法 + */ + var originSubmit = HTMLFormElement.prototype.submit; + HTMLFormElement.prototype.submit = function () { + var args = [].slice.call(arguments); + var form = this; + form.requestId = _KKJSBridgeXHR.generateXHRRequestId(); + form.requestUrl = form.action; + form.requestHref = document.location.href; + var request = { + requestId: form.requestId, + requestHref: form.requestHref, + requestUrl: form.requestUrl, + bodyType: "FormData", + formEnctype: form.enctype, + value: null + }; + if (!window.KKJSBridgeConfig.ajaxHook) { // 如果没有开启 ajax hook,则调用原始 submit + return originSubmit.apply(form, args); + } + var action = form.action; + if (!action) { // 如果 action 本身是空,则调用原始 submit + return originSubmit.apply(form, args); + } + var formData = new FormData(form); + KKJSBridgeUtil.convertFormDataToJson(formData, function (json) { + request.value = json; + _KKJSBridgeXHR.sendBodyToNativeForCache("FORM", form, originSubmit, args, request); + }); + }; + }; + return _KKJSBridgeXHR; + }()); + + var _KKJSBridgeSendBeaconHook = /** @class */ (function () { + function _KKJSBridgeSendBeaconHook() { + } + _KKJSBridgeSendBeaconHook.setupHook = function () { + if (typeof window.navigator.sendBeacon === 'function') { + var originalBeaconImpl_1 = window.navigator.sendBeacon; + window.navigator.sendBeacon = function (url, data) { + if (!data) { + return originalBeaconImpl_1(url, data); + } + var requestId = _KKJSBridgeXHR.generateXHRRequestId(); + var requestUrl = _KKJSBridgeXHR.generateNewUrlWithRequestId(url, requestId); + var requestRaw = { + requestId: requestId, + requestHref: location.href, + requestUrl: url, + bodyType: 'String', + value: null + }; + _KKJSBridgeXHR.resolveRequestBody(data).then(function (request) { + _KKJSBridgeXHR.sendBodyToNativeForCache('AJAX', window.navigator, originalBeaconImpl_1, [requestUrl, data], __assign(__assign({}, requestRaw), request)); + }); + return true; + }; + } + }; + return _KKJSBridgeSendBeaconHook; + }()); + /// var init = function () { if (window.KKJSBridge) { @@ -1531,7 +1864,7 @@ // 设置 KKJSBridgeConfig 为全局对象 window.KKJSBridgeConfig = KKJSBridgeConfig; // 设置 _KKJSBridgeXHR 为全局对象 - window._KKJSBridgeXHR = _KKJSBridgeXHR; + window._KKJSBridgeXHR = _KKJSBridgeXHR$1; // iframe 内处理来自父 window 的消息 KKJSBridgeIframe.addMessageListener(); KKJSBridgeIframe.addAjaxMessageListener(); @@ -1541,8 +1874,10 @@ _KKJSBridgeFormData.setupHook(); // 安装 cookie hook _KKJSBridgeCOOKIE.setupHook(); + // 安装 sendBeacon hook + _KKJSBridgeSendBeaconHook.setupHook(); // 安装 ajax hook - _KKJSBridgeXHR.setupHook(); + _KKJSBridgeXHR$1.setupHook(); // JSBridge 安装完毕 window.KKJSBridgeConfig.bridgeReady(); }; @@ -1551,4 +1886,4 @@ return indexold; -}))); +})); diff --git a/KKJSBridge/TS/dist/KKJSBridgeAJAXProtocolHook.js b/KKJSBridge/TS/dist/KKJSBridgeAJAXProtocolHook.js index 7ae94cb..7c68dae 100644 --- a/KKJSBridge/TS/dist/KKJSBridgeAJAXProtocolHook.js +++ b/KKJSBridge/TS/dist/KKJSBridgeAJAXProtocolHook.js @@ -1,8 +1,8 @@ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define('lib/fetch.js', factory) : - (global = global || self, global.KKJSBridge = factory()); -}(this, (function () { 'use strict'; + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.KKJSBridge = factory()); +})(this, (function () { 'use strict'; var support = { searchParams: 'URLSearchParams' in self, @@ -580,29 +580,41 @@ } /*! ***************************************************************************** - Copyright (c) Microsoft Corporation. All rights reserved. - Licensed under the Apache License, Version 2.0 (the "License"); you may not use - this file except in compliance with the License. You may obtain a copy of the - License at http://www.apache.org/licenses/LICENSE-2.0 + Copyright (c) Microsoft Corporation. - THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED - WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, - MERCHANTABLITY OR NON-INFRINGEMENT. + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. - See the Apache Version 2.0 License for specific language governing permissions - and limitations under the License. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ + var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + function __values(o) { - var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; + var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); - return { + if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; + throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); } var _KKJSBridgeFormData = /** @class */ (function () { @@ -1063,13 +1075,66 @@ return _KKJSBridgeCOOKIE; }()); - /// /** * AJAX 相关方法 */ var _KKJSBridgeXHR = /** @class */ (function () { function _KKJSBridgeXHR() { } + /** + * 统一处理 body 内容 + */ + _KKJSBridgeXHR.resolveRequestBody = function (body) { + return new Promise(function (res) { + if (!body) { + res({}); + } + else if (body instanceof ArrayBuffer) { + res({ + bodyType: 'ArrayBuffer', + // 说明是 ArrayBuffer,转成 base64 + value: KKJSBridgeUtil.convertArrayBufferToBase64(body) + }); + } + else if (body instanceof Blob) { + var bodyType_1 = 'Blob'; + var fileReader = new FileReader(); + fileReader.onload = function (ev) { + // 说明是 Blob,转成 base64 + var base64 = ev.target.result; + res({ + value: base64, + bodyType: bodyType_1 + }); + }; + fileReader.readAsDataURL(body); + } + else if (body instanceof FormData) { + // 说明是表单 + KKJSBridgeUtil.convertFormDataToJson(body, function (json) { + res({ + bodyType: 'FormData', + formEnctype: 'multipart/form-data', + value: json + }); + }); + } + else if (body instanceof URLSearchParams) { + res({ + bodyType: 'String', + formEnctype: 'application/x-www-form-urlencoded', + value: body.toString() + }); + } + else { + // 说明是字符串或者json + res({ + bodyType: 'String', + value: body + }); + } + }); + }; // 静态属性和方法 _KKJSBridgeXHR.moduleName = 'ajax'; _KKJSBridgeXHR.globalId = Math.floor(Math.random() * 100000); @@ -1242,7 +1307,7 @@ XMLHttpRequest.prototype.send = function (body) { var args = [].slice.call(arguments); var xhr = this; - var request = { + var requestRaw = { requestId: xhr.requestId, requestHref: xhr.requestHref, requestUrl: xhr.requestUrl, @@ -1255,39 +1320,17 @@ if (!window.KKJSBridgeConfig.ajaxHook) { // 如果没有开启 ajax hook,则调用原始 send return originSend.apply(xhr, args); } - if (!body) { // 没有 body,调用原始 send + if (!body || body instanceof Document) { + // 没有 body 或 body 是 Document,调用原始 send return originSend.apply(xhr, args); } - else if (body instanceof ArrayBuffer) { // 说明是 ArrayBuffer,转成 base64 - request.bodyType = "ArrayBuffer"; - request.value = KKJSBridgeUtil.convertArrayBufferToBase64(body); - } - else if (body instanceof Blob) { // 说明是 Blob,转成 base64 - request.bodyType = "Blob"; - var fileReader = new FileReader(); - fileReader.onload = function (ev) { - var base64 = ev.target.result; - request.value = base64; - _KKJSBridgeXHR.sendBodyToNativeForCache("AJAX", xhr, originSend, args, request); - }; - fileReader.readAsDataURL(body); - return; - } - else if (body instanceof FormData) { // 说明是表单 - request.bodyType = "FormData"; - request.formEnctype = "multipart/form-data"; - KKJSBridgeUtil.convertFormDataToJson(body, function (json) { - request.value = json; - _KKJSBridgeXHR.sendBodyToNativeForCache("AJAX", xhr, originSend, args, request); + else { + _KKJSBridgeXHR.resolveRequestBody(body).then(function (req) { + var request = __assign(__assign({}, requestRaw), req); + // 发送到 native 缓存起来 + _KKJSBridgeXHR.sendBodyToNativeForCache('AJAX', xhr, originSend, args, request); }); - return; - } - else { // 说明是字符串或者json - request.bodyType = "String"; - request.value = body; } - // 发送到 native 缓存起来 - _KKJSBridgeXHR.sendBodyToNativeForCache("AJAX", xhr, originSend, args, request, xhr.requestAsync); }; /** * hook form submit 方法 @@ -1324,6 +1367,35 @@ return _KKJSBridgeXHR; }()); + var _KKJSBridgeSendBeaconHook = /** @class */ (function () { + function _KKJSBridgeSendBeaconHook() { + } + _KKJSBridgeSendBeaconHook.setupHook = function () { + if (typeof window.navigator.sendBeacon === 'function') { + var originalBeaconImpl_1 = window.navigator.sendBeacon; + window.navigator.sendBeacon = function (url, data) { + if (!data) { + return originalBeaconImpl_1(url, data); + } + var requestId = _KKJSBridgeXHR.generateXHRRequestId(); + var requestUrl = _KKJSBridgeXHR.generateNewUrlWithRequestId(url, requestId); + var requestRaw = { + requestId: requestId, + requestHref: location.href, + requestUrl: url, + bodyType: 'String', + value: null + }; + _KKJSBridgeXHR.resolveRequestBody(data).then(function (request) { + _KKJSBridgeXHR.sendBodyToNativeForCache('AJAX', window.navigator, originalBeaconImpl_1, [requestUrl, data], __assign(__assign({}, requestRaw), request)); + }); + return true; + }; + } + }; + return _KKJSBridgeSendBeaconHook; + }()); + /// var init = function () { if (window.KKJSBridge) { @@ -1389,6 +1461,8 @@ _KKJSBridgeFormData.setupHook(); // 安装 cookie hook _KKJSBridgeCOOKIE.setupHook(); + // 安装 sendBeacon hook + _KKJSBridgeSendBeaconHook.setupHook(); // 安装 ajax hook _KKJSBridgeXHR.setupHook(); // JSBridge 安装完毕 @@ -1399,4 +1473,4 @@ return index; -}))); +})); diff --git a/KKJSBridge/TS/src/hook/KKJSBridgeAjaxProtocolHook.ts b/KKJSBridge/TS/src/hook/KKJSBridgeAjaxProtocolHook.ts index 2b68f40..541c124 100644 --- a/KKJSBridge/TS/src/hook/KKJSBridgeAjaxProtocolHook.ts +++ b/KKJSBridge/TS/src/hook/KKJSBridgeAjaxProtocolHook.ts @@ -89,6 +89,56 @@ export class _KKJSBridgeXHR { return newUrl; } + /** + * 统一处理 body 内容 + */ + public static resolveRequestBody(body?: BodyInit): Promise> { + return new Promise(res => { + if (!body) { + res({}); + } else if (body instanceof ArrayBuffer) { + res({ + bodyType: 'ArrayBuffer', + // 说明是 ArrayBuffer,转成 base64 + value: KKJSBridgeUtil.convertArrayBufferToBase64(body) + }); + } else if (body instanceof Blob) { + const bodyType: KK.AJAXBodyCacheRequest['bodyType'] = 'Blob'; + const fileReader: FileReader = new FileReader(); + fileReader.onload = function (this: FileReader, ev: ProgressEvent) { + // 说明是 Blob,转成 base64 + const base64: string = (ev.target as any).result; + res({ + value: base64, + bodyType + }); + }; + fileReader.readAsDataURL(body); + } else if (body instanceof FormData) { + // 说明是表单 + KKJSBridgeUtil.convertFormDataToJson(body, (json: string) => { + res({ + bodyType: 'FormData', + formEnctype: 'multipart/form-data', + value: json + }); + }); + } else if (body instanceof URLSearchParams) { + res({ + bodyType: 'String', + formEnctype: 'application/x-www-form-urlencoded', + value: body.toString() + }); + } else { + // 说明是字符串或者json + res({ + bodyType: 'String', + value: body + }); + } + }); + } + /** * 是否是非正常的 http 请求。比如 url: blob:https:// 场景下,去发送 XMLHTTPRequest,会导致请求失败 */ @@ -197,56 +247,39 @@ export class _KKJSBridgeXHR { let originSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function(body?: string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream) { - let args: any = [].slice.call(arguments); - let xhr: XMLHttpRequest = this; - let request: KK.AJAXBodyCacheRequest = { - requestId: xhr.requestId, - requestHref: xhr.requestHref, - requestUrl: xhr.requestUrl, - bodyType: "String", - value: null - }; - - if (_KKJSBridgeXHR.isNonNormalHttpRequest(xhr.requestUrl, xhr.requestMethod)) {// 如果是非正常请求,则调用原始 send - return originSend.apply(xhr, args); - } - - if (!window.KKJSBridgeConfig.ajaxHook) {// 如果没有开启 ajax hook,则调用原始 send - return originSend.apply(xhr, args); - } + let args: any = [].slice.call(arguments); + let xhr: XMLHttpRequest = this; + let requestRaw: KK.AJAXBodyCacheRequest = { + requestId: xhr.requestId, + requestHref: xhr.requestHref, + requestUrl: xhr.requestUrl, + bodyType: "String", + value: null + }; + + if (_KKJSBridgeXHR.isNonNormalHttpRequest(xhr.requestUrl, xhr.requestMethod)) {// 如果是非正常请求,则调用原始 send + return originSend.apply(xhr, args); + } + + if (!window.KKJSBridgeConfig.ajaxHook) {// 如果没有开启 ajax hook,则调用原始 send + return originSend.apply(xhr, args); + } - if (!body) {// 没有 body,调用原始 send - return originSend.apply(xhr, args); - } else if (body instanceof ArrayBuffer) {// 说明是 ArrayBuffer,转成 base64 - request.bodyType = "ArrayBuffer"; - request.value = KKJSBridgeUtil.convertArrayBufferToBase64(body); - } else if (body instanceof Blob) {// 说明是 Blob,转成 base64 - request.bodyType = "Blob"; - let fileReader: FileReader = new FileReader(); - fileReader.onload = function(this: FileReader, ev: ProgressEvent) { - let base64: string = (ev.target as any).result; - request.value = base64; - _KKJSBridgeXHR.sendBodyToNativeForCache("AJAX", xhr, originSend, args, request); - }; + if (!body || body instanceof Document) { + // 没有 body 或 body 是 Document,调用原始 send + return originSend.apply(xhr, args); + } else { + _KKJSBridgeXHR.resolveRequestBody(body).then(req => { + const request = { + ...requestRaw, + ...req + }; - fileReader.readAsDataURL(body); - return; - } else if (body instanceof FormData) {// 说明是表单 - request.bodyType = "FormData"; - request.formEnctype = "multipart/form-data"; - KKJSBridgeUtil.convertFormDataToJson(body, (json: any) => { - request.value = json; - _KKJSBridgeXHR.sendBodyToNativeForCache("AJAX", xhr, originSend, args, request); - }); - return; - } else {// 说明是字符串或者json - request.bodyType = "String"; - request.value = body; - } - - // 发送到 native 缓存起来 - _KKJSBridgeXHR.sendBodyToNativeForCache("AJAX", xhr, originSend, args, request, xhr.requestAsync); - } as any; + // 发送到 native 缓存起来 + _KKJSBridgeXHR.sendBodyToNativeForCache('AJAX', xhr, originSend, args, request); + }); + } + }; /** * hook form submit 方法 @@ -284,4 +317,4 @@ export class _KKJSBridgeXHR { }); }; } -} +} \ No newline at end of file diff --git a/KKJSBridge/TS/src/hook/KKJSBridgeSendBeaconHook.ts b/KKJSBridge/TS/src/hook/KKJSBridgeSendBeaconHook.ts new file mode 100644 index 0000000..5dfc512 --- /dev/null +++ b/KKJSBridge/TS/src/hook/KKJSBridgeSendBeaconHook.ts @@ -0,0 +1,33 @@ +import {_KKJSBridgeXHR} from './KKJSBridgeAjaxProtocolHook'; + +export class _KKJSBridgeSendBeaconHook { + public static setupHook() { + if (typeof window.navigator.sendBeacon === 'function') { + const originalBeaconImpl = window.navigator.sendBeacon; + window.navigator.sendBeacon = function (url: string, data?: BodyInit): boolean { + if (!data) { + return originalBeaconImpl(url, data); + } + + const requestId = _KKJSBridgeXHR.generateXHRRequestId(); + const requestUrl = _KKJSBridgeXHR.generateNewUrlWithRequestId(url, requestId); + const requestRaw: KK.AJAXBodyCacheRequest = { + requestId: requestId, + requestHref: location.href, + requestUrl: url, + bodyType: 'String', + value: null + }; + + _KKJSBridgeXHR.resolveRequestBody(data).then(request => { + _KKJSBridgeXHR.sendBodyToNativeForCache('AJAX', window.navigator, originalBeaconImpl, [requestUrl, data], { + ...requestRaw, + ...request + }); + }); + + return true; + }; + } + } +} diff --git a/KKJSBridge/TS/src/index.ts b/KKJSBridge/TS/src/index.ts index a5aea1e..9bdd5a9 100644 --- a/KKJSBridge/TS/src/index.ts +++ b/KKJSBridge/TS/src/index.ts @@ -5,6 +5,7 @@ import { KKJSBridge } from "./bridge/KKJSBridge" import { _KKJSBridgeFormData } from "./hook/KKJSBridgeFormDataHook" import { _KKJSBridgeCOOKIE } from "./hook/KKJSBridgeCookieHook" import { _KKJSBridgeXHR } from "./hook/KKJSBridgeAjaxProtocolHook" +import { _KKJSBridgeSendBeaconHook } from "./hook/KKJSBridgeSendBeaconHook" var init = function() { if (window.KKJSBridge) { @@ -76,6 +77,9 @@ var init = function() { // 安装 cookie hook _KKJSBridgeCOOKIE.setupHook(); + // 安装 sendBeacon hook + _KKJSBridgeSendBeaconHook.setupHook(); + // 安装 ajax hook _KKJSBridgeXHR.setupHook(); diff --git a/KKJSBridge/TS/src/indexold.ts b/KKJSBridge/TS/src/indexold.ts index 70c25d4..f7547d5 100644 --- a/KKJSBridge/TS/src/indexold.ts +++ b/KKJSBridge/TS/src/indexold.ts @@ -5,6 +5,7 @@ import { KKJSBridge } from "./bridge/KKJSBridge" import { _KKJSBridgeFormData } from "./hook/KKJSBridgeFormDataHook" import { _KKJSBridgeCOOKIE } from "./hook/KKJSBridgeCookieHook" import { _KKJSBridgeXHR } from "./hook/KKJSBridgeAjaxHook" +import { _KKJSBridgeSendBeaconHook } from "./hook/KKJSBridgeSendBeaconHook" var init = function() { if (window.KKJSBridge) { @@ -76,6 +77,9 @@ var init = function() { // 安装 cookie hook _KKJSBridgeCOOKIE.setupHook(); + // 安装 sendBeacon hook + _KKJSBridgeSendBeaconHook.setupHook(); + // 安装 ajax hook _KKJSBridgeXHR.setupHook();