diff --git a/CHANGELOG.md b/CHANGELOG.md index acc28539..87e4de10 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ - Fixed wrong mapping of `NavigationAction` class on Android for `androidHasGesture` and `androidIsRedirect` properties - Fixed "Pull to refresh creating problem in some webpages on Android" [#719](https://github.com/pichillilorenzo/flutter_inappwebview/issues/719) - Fixed iOS sometimes `scrollView.contentSize` doesn't fit all the `frame.size` available +- Fixed ajax and fetch interceptor when the data/body sent is not a string +- Fixed "InAppLocalhostServer - Error: type 'List' is not a subtype of type 'List' in type cast" [#724](https://github.com/pichillilorenzo/flutter_inappwebview/issues/724) + +### BREAKING CHANGES + +- `FetchRequest.body` is a dynamic type now ## 5.1.0+4 diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/Util.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/Util.java index 4a0b45b1..d88d3d6b 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/Util.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/Util.java @@ -274,4 +274,17 @@ public class Util { public static String replaceAll(String s, String oldString, String newString) { return TextUtils.join(newString, s.split(Pattern.quote(oldString))); } + + public static void log(String tag, String message) { + // Split by line, then ensure each line can fit into Log's maximum length. + for (int i = 0, length = message.length(); i < length; i++) { + int newline = message.indexOf('\n', i); + newline = newline != -1 ? newline : length; + do { + int end = Math.min(newline, i + 4000); + Log.d(tag, message.substring(i, end)); + i = end; + } while (i < newline); + } + } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptAjaxRequestJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptAjaxRequestJS.java index dba3246e..9dbcba6d 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptAjaxRequestJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptAjaxRequestJS.java @@ -183,46 +183,63 @@ public class InterceptAjaxRequestJS { " this.addEventListener('error', handleEvent);" + " this.addEventListener('abort', handleEvent);" + " this.addEventListener('timeout', handleEvent);" + - " var ajaxRequest = {" + - " data: data," + - " method: this._flutter_inappwebview_method," + - " url: this._flutter_inappwebview_url," + - " isAsync: this._flutter_inappwebview_isAsync," + - " user: this._flutter_inappwebview_user," + - " password: this._flutter_inappwebview_password," + - " withCredentials: this.withCredentials," + - " headers: this._flutter_inappwebview_request_headers," + - " responseType: this.responseType" + - " };" + - " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) {" + - " if (result != null) {" + - " switch (result.action) {" + - " case 0:" + - " self.abort();" + - " return;" + - " };" + - " data = result.data;" + - " self.withCredentials = result.withCredentials;" + - " if (result.responseType != null) {" + - " self.responseType = result.responseType;" + - " };" + - " for (var header in result.headers) {" + - " var value = result.headers[header];" + - " var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header];" + - " if (flutter_inappwebview_value == null) {" + - " self._flutter_inappwebview_request_headers[header] = value;" + - " } else {" + - " self._flutter_inappwebview_request_headers[header] += ', ' + value;" + + " " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertBodyRequest(data).then(function(data) {" + + " var ajaxRequest = {" + + " data: data," + + " method: self._flutter_inappwebview_method," + + " url: self._flutter_inappwebview_url," + + " isAsync: self._flutter_inappwebview_isAsync," + + " user: self._flutter_inappwebview_user," + + " password: self._flutter_inappwebview_password," + + " withCredentials: self.withCredentials," + + " headers: self._flutter_inappwebview_request_headers," + + " responseType: self.responseType" + + " };" + + " window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) {" + + " if (result != null) {" + + " switch (result.action) {" + + " case 0:" + + " self.abort();" + + " return;" + + " };" + + " if (result.data != null && !" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.data) && result.data.length > 0) {" + + " var bodyString = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".arrayBufferToString(result.data);" + + " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isBodyFormData(bodyString)) {" + + " var formDataContentType = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".getFormDataContentType(bodyString);" + + " if (result.headers != null) {" + + " result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];" + + " } else {" + + " result.headers = { 'Content-Type': formDataContentType };" + + " }" + + " }" + + " }" + + " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.data) || result.data == null) {" + + " data = result.data;" + + " } else if (result.data.length > 0) {" + + " data = new Uint8Array(result.data);" + + " }" + + " self.withCredentials = result.withCredentials;" + + " if (result.responseType != null) {" + + " self.responseType = result.responseType;" + + " };" + + " for (var header in result.headers) {" + + " var value = result.headers[header];" + + " var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header];" + + " if (flutter_inappwebview_value == null) {" + + " self._flutter_inappwebview_request_headers[header] = value;" + + " } else {" + + " self._flutter_inappwebview_request_headers[header] += ', ' + value;" + + " }" + + " setRequestHeader.call(self, header, value);" + + " };" + + " if ((self._flutter_inappwebview_method != result.method && result.method != null) || (self._flutter_inappwebview_url != result.url && result.url != null)) {" + + " self.abort();" + + " self.open(result.method, result.url, result.isAsync, result.user, result.password);" + + " return;" + " }" + - " setRequestHeader.call(self, header, value);" + - " };" + - " if ((self._flutter_inappwebview_method != result.method && result.method != null) || (self._flutter_inappwebview_url != result.url && result.url != null)) {" + - " self.abort();" + - " self.open(result.method, result.url, result.isAsync, result.user, result.password);" + - " return;" + " }" + - " }" + - " send.call(self, data);" + + " send.call(self, data);" + + " });" + " });" + " } else {" + " send.call(this, data);" + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptFetchRequestJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptFetchRequestJS.java index bac04c0d..f5d833eb 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptFetchRequestJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/InterceptFetchRequestJS.java @@ -21,68 +21,6 @@ public class InterceptFetchRequestJS { " if (fetch == null) {" + " return;" + " }" + - " function convertHeadersToJson(headers) {" + - " var headersObj = {};" + - " for (var header of headers.keys()) {" + - " var value = headers.get(header);" + - " headersObj[header] = value;" + - " }" + - " return headersObj;" + - " }" + - " function convertJsonToHeaders(headersJson) {" + - " return new Headers(headersJson);" + - " }" + - " function convertBodyToArray(body) {" + - " return new Response(body).arrayBuffer().then(function(arrayBuffer) {" + - " var arr = Array.from(new Uint8Array(arrayBuffer));" + - " return arr;" + - " })" + - " }" + - " function convertArrayIntBodyToUint8Array(arrayIntBody) {" + - " return new Uint8Array(arrayIntBody);" + - " }" + - " function convertCredentialsToJson(credentials) {" + - " var credentialsObj = {};" + - " if (window.FederatedCredential != null && credentials instanceof FederatedCredential) {" + - " credentialsObj.type = credentials.type;" + - " credentialsObj.id = credentials.id;" + - " credentialsObj.name = credentials.name;" + - " credentialsObj.protocol = credentials.protocol;" + - " credentialsObj.provider = credentials.provider;" + - " credentialsObj.iconURL = credentials.iconURL;" + - " } else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) {" + - " credentialsObj.type = credentials.type;" + - " credentialsObj.id = credentials.id;" + - " credentialsObj.name = credentials.name;" + - " credentialsObj.password = credentials.password;" + - " credentialsObj.iconURL = credentials.iconURL;" + - " } else {" + - " credentialsObj.type = 'default';" + - " credentialsObj.value = credentials;" + - " }" + - " }" + - " function convertJsonToCredential(credentialsJson) {" + - " var credentials;" + - " if (window.FederatedCredential != null && credentialsJson.type === 'federated') {" + - " credentials = new FederatedCredential({" + - " id: credentialsJson.id," + - " name: credentialsJson.name," + - " protocol: credentialsJson.protocol," + - " provider: credentialsJson.provider," + - " iconURL: credentialsJson.iconURL" + - " });" + - " } else if (window.PasswordCredential != null && credentialsJson.type === 'password') {" + - " credentials = new PasswordCredential({" + - " id: credentialsJson.id," + - " name: credentialsJson.name," + - " password: credentialsJson.password," + - " iconURL: credentialsJson.iconURL" + - " });" + - " } else {" + - " credentials = credentialsJson;" + - " }" + - " return credentials;" + - " }" + " window.fetch = async function(resource, init) {" + " var w = (window.top == null || window.top === window) ? window : window.top;" + " if (w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE + " == null || w." + FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE + " == true) {" + @@ -114,7 +52,7 @@ public class InterceptFetchRequestJS { " fetchRequest.integrity = resource.integrity;" + " fetchRequest.keepalive = resource.keepalive;" + " } else {" + - " fetchRequest.url = resource;" + + " fetchRequest.url = resource != null ? resource.toString() : null;" + " if (init != null) {" + " fetchRequest.method = init.method;" + " fetchRequest.headers = init.headers;" + @@ -130,10 +68,10 @@ public class InterceptFetchRequestJS { " }" + " }" + " if (fetchRequest.headers instanceof Headers) {" + - " fetchRequest.headers = convertHeadersToJson(fetchRequest.headers);" + + " fetchRequest.headers = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertHeadersToJson(fetchRequest.headers);" + " }" + - " fetchRequest.credentials = convertCredentialsToJson(fetchRequest.credentials);" + - " return convertBodyToArray(fetchRequest.body).then(function(body) {" + + " fetchRequest.credentials = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertCredentialsToJson(fetchRequest.credentials);" + + " return " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertBodyRequest(fetchRequest.body).then(function(body) {" + " fetchRequest.body = body;" + " return window." + JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME + ".callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) {" + " if (result != null) {" + @@ -150,7 +88,18 @@ public class InterceptFetchRequestJS { " controller.abort();" + " break;" + " }" + - " resource = (result.url != null) ? result.url : resource;" + + " if (result.body != null && !" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.body) && result.body.length > 0) {" + + " var bodyString = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".arrayBufferToString(result.body);" + + " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isBodyFormData(bodyString)) {" + + " var formDataContentType = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".getFormDataContentType(bodyString);" + + " if (result.headers != null) {" + + " result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];" + + " } else {" + + " result.headers = { 'Content-Type': formDataContentType };" + + " }" + + " }" + + " }" + + " resource = result.url;" + " if (init == null) {" + " init = {};" + " }" + @@ -158,16 +107,18 @@ public class InterceptFetchRequestJS { " init.method = result.method;" + " }" + " if (result.headers != null && Object.keys(result.headers).length > 0) {" + - " init.headers = convertJsonToHeaders(result.headers);" + + " init.headers = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertJsonToHeaders(result.headers);" + " }" + - " if (result.body != null && result.body.length > 0) {" + - " init.body = convertArrayIntBodyToUint8Array(result.body);" + + " if (" + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".isString(result.body) || result.body == null) {" + + " init.body = result.body;" + + " } else if (result.body.length > 0) {" + + " init.body = new Uint8Array(result.body);" + " }" + " if (result.mode != null && result.mode.length > 0) {" + " init.mode = result.mode;" + " }" + " if (result.credentials != null) {" + - " init.credentials = convertJsonToCredential(result.credentials);" + + " init.credentials = " + JavaScriptBridgeJS.JAVASCRIPT_UTIL_VAR_NAME + ".convertJsonToCredential(result.credentials);" + " }" + " if (result.cache != null && result.cache.length > 0) {" + " init.cache = result.cache;" + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/JavaScriptBridgeJS.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/JavaScriptBridgeJS.java index 834789aa..4cd275d2 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/JavaScriptBridgeJS.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/plugin_scripts_js/JavaScriptBridgeJS.java @@ -14,6 +14,203 @@ public class JavaScriptBridgeJS { true ); + public static final String JAVASCRIPT_UTIL_VAR_NAME = "window." + JAVASCRIPT_BRIDGE_NAME + "._Util"; + + public static final String UTIL_JS_SOURCE = JAVASCRIPT_UTIL_VAR_NAME + " = {" + + " support: {" + + " searchParams: 'URLSearchParams' in window," + + " iterable: 'Symbol' in window && 'iterator' in Symbol," + + " blob:" + + " 'FileReader' in window &&" + + " 'Blob' in window &&" + + " (function() {" + + " try {" + + " new Blob();" + + " return true;" + + " } catch (e) {" + + " return false;" + + " }" + + " })()," + + " formData: 'FormData' in window," + + " arrayBuffer: 'ArrayBuffer' in window" + + " }," + + " isDataView: function(obj) {" + + " return obj && DataView.prototype.isPrototypeOf(obj);" + + " }," + + " fileReaderReady: function(reader) {" + + " return new Promise(function(resolve, reject) {" + + " reader.onload = function() {" + + " resolve(reader.result);" + + " };" + + " reader.onerror = function() {" + + " reject(reader.error);" + + " };" + + " });" + + " }," + + " readBlobAsArrayBuffer: function(blob) {" + + " var reader = new FileReader();" + + " var promise = " + JAVASCRIPT_UTIL_VAR_NAME + ".fileReaderReady(reader);" + + " reader.readAsArrayBuffer(blob);" + + " return promise;" + + " }," + + " convertBodyToArrayBuffer: function(body) {" + + " var viewClasses = [" + + " '[object Int8Array]'," + + " '[object Uint8Array]'," + + " '[object Uint8ClampedArray]'," + + " '[object Int16Array]'," + + " '[object Uint16Array]'," + + " '[object Int32Array]'," + + " '[object Uint32Array]'," + + " '[object Float32Array]'," + + " '[object Float64Array]'" + + " ];" + + " var isArrayBufferView = null;" + + " if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.arrayBuffer) {" + + " isArrayBufferView =" + + " ArrayBuffer.isView ||" + + " function(obj) {" + + " return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;" + + " };" + + " }" + + " var bodyUsed = false;" + + " this._bodyInit = body;" + + " if (!body) {" + + " this._bodyText = '';" + + " } else if (typeof body === 'string') {" + + " this._bodyText = body;" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.blob && Blob.prototype.isPrototypeOf(body)) {" + + " this._bodyBlob = body;" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.formData && FormData.prototype.isPrototypeOf(body)) {" + + " this._bodyFormData = body;" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {" + + " this._bodyText = body.toString();" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.arrayBuffer && " + JAVASCRIPT_UTIL_VAR_NAME + ".support.blob && " + JAVASCRIPT_UTIL_VAR_NAME + ".isDataView(body)) {" + + " this._bodyArrayBuffer = bufferClone(body.buffer);" + + " this._bodyInit = new Blob([this._bodyArrayBuffer]);" + + " } else if (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {" + + " this._bodyArrayBuffer = bufferClone(body);" + + " } else {" + + " this._bodyText = body = Object.prototype.toString.call(body);" + + " }" + + " this.blob = function () {" + + " if (bodyUsed) {" + + " return Promise.reject(new TypeError('Already read'));" + + " }" + + " bodyUsed = true;" + + " if (this._bodyBlob) {" + + " return Promise.resolve(this._bodyBlob);" + + " } else if (this._bodyArrayBuffer) {" + + " return Promise.resolve(new Blob([this._bodyArrayBuffer]));" + + " } else if (this._bodyFormData) {" + + " throw new Error('could not read FormData body as blob');" + + " } else {" + + " return Promise.resolve(new Blob([this._bodyText]));" + + " }" + + " };" + + " if (this._bodyArrayBuffer) {" + + " if (bodyUsed) {" + + " return Promise.reject(new TypeError('Already read'));" + + " }" + + " bodyUsed = true;" + + " if (ArrayBuffer.isView(this._bodyArrayBuffer)) {" + + " return Promise.resolve(" + + " this._bodyArrayBuffer.buffer.slice(" + + " this._bodyArrayBuffer.byteOffset," + + " this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength" + + " )" + + " );" + + " } else {" + + " return Promise.resolve(this._bodyArrayBuffer);" + + " }" + + " }" + + " return this.blob().then(" + JAVASCRIPT_UTIL_VAR_NAME + ".readBlobAsArrayBuffer);" + + " }," + + " isString: function(variable) {" + + " return typeof variable === 'string' || variable instanceof String;" + + " }," + + " convertBodyRequest: function(body) {" + + " if (body == null) {" + + " return new Promise((resolve, reject) => resolve(null));" + + " }" + + " if (" + JAVASCRIPT_UTIL_VAR_NAME + ".isString(body) || (" + JAVASCRIPT_UTIL_VAR_NAME + ".support.searchParams && body instanceof URLSearchParams)) {" + + " return new Promise((resolve, reject) => resolve(body.toString()));" + + " }" + + " if (window.Response != null) {" + + " return new Response(body).arrayBuffer().then(function(arrayBuffer) {" + + " return Array.from(new Uint8Array(arrayBuffer));" + + " });" + + " }" + + " return " + JAVASCRIPT_UTIL_VAR_NAME + ".convertBodyToArrayBuffer(body).then(function(arrayBuffer) {" + + " return Array.from(new Uint8Array(arrayBuffer));" + + " });" + + " }," + + " arrayBufferToString: function(arrayBuffer) {" + + " return String.fromCharCode.apply(String, arrayBuffer);" + + " }," + + " isBodyFormData: function(bodyString) {" + + " return bodyString.indexOf('------WebKitFormBoundary') >= 0;" + + " }," + + " getFormDataContentType: function(bodyString) {" + + " var boundary = bodyString.substr(2, 40);" + + " return 'multipart/form-data; boundary=' + boundary;" + + " }," + + " convertHeadersToJson: function(headers) {" + + " var headersObj = {};" + + " for (var header of headers.keys()) {" + + " var value = headers.get(header);" + + " headersObj[header] = value;" + + " }" + + " return headersObj;" + + " }," + + " convertJsonToHeaders: function(headersJson) {" + + " return new Headers(headersJson);" + + " }," + + " convertCredentialsToJson: function(credentials) {" + + " var credentialsObj = {};" + + " if (window.FederatedCredential != null && credentials instanceof FederatedCredential) {" + + " credentialsObj.type = credentials.type;" + + " credentialsObj.id = credentials.id;" + + " credentialsObj.name = credentials.name;" + + " credentialsObj.protocol = credentials.protocol;" + + " credentialsObj.provider = credentials.provider;" + + " credentialsObj.iconURL = credentials.iconURL;" + + " } else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) {" + + " credentialsObj.type = credentials.type;" + + " credentialsObj.id = credentials.id;" + + " credentialsObj.name = credentials.name;" + + " credentialsObj.password = credentials.password;" + + " credentialsObj.iconURL = credentials.iconURL;" + + " } else {" + + " credentialsObj.type = 'default';" + + " credentialsObj.value = credentials;" + + " }" + + " return credentialsObj;" + + " }," + + " convertJsonToCredential: function(credentialsJson) {" + + " var credentials;" + + " if (window.FederatedCredential != null && credentialsJson.type === 'federated') {" + + " credentials = new FederatedCredential({" + + " id: credentialsJson.id," + + " name: credentialsJson.name," + + " protocol: credentialsJson.protocol," + + " provider: credentialsJson.provider," + + " iconURL: credentialsJson.iconURL" + + " });" + + " } else if (window.PasswordCredential != null && credentialsJson.type === 'password') {" + + " credentials = new PasswordCredential({" + + " id: credentialsJson.id," + + " name: credentialsJson.name," + + " password: credentialsJson.password," + + " iconURL: credentialsJson.iconURL" + + " });" + + " } else {" + + " credentials = credentialsJson.value == null ? undefined : credentialsJson.value;" + + " }" + + " return credentials;" + + " }" + + "};"; + public static final String JAVASCRIPT_BRIDGE_JS_SOURCE = "if (window.top == null || window.top === window) {" + " window." + JAVASCRIPT_BRIDGE_NAME + ".callHandler = function() {" + " var _callHandlerID = setTimeout(function(){});" + @@ -30,8 +227,9 @@ public class JavaScriptBridgeJS { " return new Promise(function(resolve, reject) {" + " window.top." + JAVASCRIPT_BRIDGE_NAME + "[_callHandlerID] = resolve;" + " });" + - " };"+ - "}"; + " };" + + "}" + + UTIL_JS_SOURCE; public static final String PLATFORM_READY_JS_SOURCE = "(function() {" + " if ((window.top == null || window.top === window) && window." + JAVASCRIPT_BRIDGE_NAME + "._platformReady == null) {" + diff --git a/example/integration_test/webview_flutter_test.dart b/example/integration_test/webview_flutter_test.dart index 17d47e09..592a9f1a 100644 --- a/example/integration_test/webview_flutter_test.dart +++ b/example/integration_test/webview_flutter_test.dart @@ -1706,20 +1706,21 @@ void main() { skip: !Platform.isAndroid, ); - testWidgets('intercept ajax request', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); - final Completer shouldInterceptAjaxPostRequestCompleter = - Completer(); - final Completer> onAjaxReadyStateChangeCompleter = - Completer>(); - final Completer> onAjaxProgressCompleter = - Completer>(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialData: InAppWebViewInitialData(data: """ + group('intercept ajax request', () { + testWidgets('send string data', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer shouldInterceptAjaxPostRequestCompleter = + Completer(); + final Completer> onAjaxReadyStateChangeCompleter = + Completer>(); + final Completer> onAjaxProgressCompleter = + Completer>(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialData: InAppWebViewInitialData(data: """ @@ -1736,117 +1737,347 @@ void main() { xhttp.open("POST", "http://${environment["NODE_SERVER_IP"]}:8082/test-ajax-post"); xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhttp.send("firstname=Foo&lastname=Bar"); - - var xhttp2 = new XMLHttpRequest(); - xhttp2.open("GET", "http://${environment["NODE_SERVER_IP"]}:8082/test-download-file"); - xhttp2.send(); }); """), - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions( - clearCache: true, - useShouldInterceptAjaxRequest: true, - )), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - shouldInterceptAjaxRequest: (controller, ajaxRequest) async { - if (ajaxRequest.url!.toString().endsWith("/test-ajax-post")) { + initialOptions: InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + clearCache: true, + useShouldInterceptAjaxRequest: true, + )), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + shouldInterceptAjaxRequest: (controller, ajaxRequest) async { + expect(ajaxRequest.data, "firstname=Foo&lastname=Bar"); + ajaxRequest.responseType = 'json'; ajaxRequest.data = "firstname=Foo2&lastname=Bar2"; shouldInterceptAjaxPostRequestCompleter.complete(controller); - } - return ajaxRequest; - }, - onAjaxReadyStateChange: (controller, ajaxRequest) async { - if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && - ajaxRequest.status == 200 && - ajaxRequest.url!.toString().endsWith("/test-ajax-post")) { - Map res = ajaxRequest.response; - onAjaxReadyStateChangeCompleter.complete(res); - } - return AjaxRequestAction.PROCEED; - }, - onAjaxProgress: (controller, ajaxRequest) async { - if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD && - ajaxRequest.url!.toString().endsWith("/test-ajax-post")) { - Map res = ajaxRequest.response; - onAjaxProgressCompleter.complete(res); - } - return AjaxRequestAction.PROCEED; - }, + return ajaxRequest; + }, + onAjaxReadyStateChange: (controller, ajaxRequest) async { + if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && + ajaxRequest.status == 200) { + Map res = ajaxRequest.response; + onAjaxReadyStateChangeCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + onAjaxProgress: (controller, ajaxRequest) async { + if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD) { + Map res = ajaxRequest.response; + onAjaxProgressCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + ), ), - ), - ); + ); - await shouldInterceptAjaxPostRequestCompleter.future; - final Map onAjaxReadyStateChangeValue = - await onAjaxReadyStateChangeCompleter.future; - final Map onAjaxProgressValue = - await onAjaxProgressCompleter.future; + await shouldInterceptAjaxPostRequestCompleter.future; + final Map onAjaxReadyStateChangeValue = + await onAjaxReadyStateChangeCompleter.future; + final Map onAjaxProgressValue = + await onAjaxProgressCompleter.future; - expect( - mapEquals(onAjaxReadyStateChangeValue, - {'firstname': 'Foo2', 'lastname': 'Bar2'}), - true); - expect( - mapEquals( - onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), - true); + expect( + mapEquals(onAjaxReadyStateChangeValue, + {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + expect( + mapEquals( + onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + }); + + testWidgets('send json data', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer shouldInterceptAjaxPostRequestCompleter = + Completer(); + final Completer> onAjaxReadyStateChangeCompleter = + Completer>(); + final Completer> onAjaxProgressCompleter = + Completer>(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialData: InAppWebViewInitialData(data: """ + + + + + + + InAppWebViewAjaxTest + + +

InAppWebViewAjaxTest

+ + + + """), + initialOptions: InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + clearCache: true, + useShouldInterceptAjaxRequest: true, + )), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + shouldInterceptAjaxRequest: (controller, ajaxRequest) async { + expect(ajaxRequest.data, '{"firstname":"Foo","lastname":"Bar"}'); + + ajaxRequest.responseType = 'json'; + ajaxRequest.data = "{'firstname': 'Foo2', 'lastname': 'Bar2'}"; + shouldInterceptAjaxPostRequestCompleter.complete(controller); + return ajaxRequest; + }, + onAjaxReadyStateChange: (controller, ajaxRequest) async { + if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && + ajaxRequest.status == 200) { + Map res = ajaxRequest.response; + onAjaxReadyStateChangeCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + onAjaxProgress: (controller, ajaxRequest) async { + if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD) { + Map res = ajaxRequest.response; + onAjaxProgressCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + ), + ), + ); + + await shouldInterceptAjaxPostRequestCompleter.future; + final Map onAjaxReadyStateChangeValue = + await onAjaxReadyStateChangeCompleter.future; + final Map onAjaxProgressValue = + await onAjaxProgressCompleter.future; + + expect( + mapEquals(onAjaxReadyStateChangeValue, + {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + expect( + mapEquals( + onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + }); + + testWidgets('send URLSearchParams data', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer shouldInterceptAjaxPostRequestCompleter = + Completer(); + final Completer> onAjaxReadyStateChangeCompleter = + Completer>(); + final Completer> onAjaxProgressCompleter = + Completer>(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialData: InAppWebViewInitialData(data: """ + + + + + + + InAppWebViewAjaxTest + + +

InAppWebViewAjaxTest

+ + + + """), + initialOptions: InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + clearCache: true, + useShouldInterceptAjaxRequest: true, + )), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + shouldInterceptAjaxRequest: (controller, ajaxRequest) async { + expect(ajaxRequest.data, "firstname=Foo&lastname=Bar"); + + ajaxRequest.responseType = 'json'; + ajaxRequest.data = "firstname=Foo2&lastname=Bar2"; + shouldInterceptAjaxPostRequestCompleter.complete(controller); + return ajaxRequest; + }, + onAjaxReadyStateChange: (controller, ajaxRequest) async { + if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && + ajaxRequest.status == 200) { + Map res = ajaxRequest.response; + onAjaxReadyStateChangeCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + onAjaxProgress: (controller, ajaxRequest) async { + if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD) { + Map res = ajaxRequest.response; + onAjaxProgressCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + ), + ), + ); + + await shouldInterceptAjaxPostRequestCompleter.future; + final Map onAjaxReadyStateChangeValue = + await onAjaxReadyStateChangeCompleter.future; + final Map onAjaxProgressValue = + await onAjaxProgressCompleter.future; + + expect( + mapEquals(onAjaxReadyStateChangeValue, + {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + expect( + mapEquals( + onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + }); + + testWidgets('send FormData', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer shouldInterceptAjaxPostRequestCompleter = + Completer(); + final Completer> onAjaxReadyStateChangeCompleter = + Completer>(); + final Completer> onAjaxProgressCompleter = + Completer>(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialData: InAppWebViewInitialData(data: """ + + + + + + + InAppWebViewAjaxTest + + +

InAppWebViewAjaxTest

+ + + + """), + initialOptions: InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + clearCache: true, + useShouldInterceptAjaxRequest: true, + )), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + shouldInterceptAjaxRequest: (controller, ajaxRequest) async { + expect(ajaxRequest.data, isNotNull); + + var body = ajaxRequest.data.cast(); + var bodyString = String.fromCharCodes(body); + expect(bodyString.indexOf("WebKitFormBoundary") >= 0, true); + + ajaxRequest.data = utf8.encode(bodyString.replaceFirst("Foo", "Foo2").replaceFirst("Bar", "Bar2")); + ajaxRequest.responseType = 'json'; + shouldInterceptAjaxPostRequestCompleter.complete(controller); + return ajaxRequest; + }, + onAjaxReadyStateChange: (controller, ajaxRequest) async { + if (ajaxRequest.readyState == AjaxRequestReadyState.DONE && + ajaxRequest.status == 200) { + Map res = ajaxRequest.response; + onAjaxReadyStateChangeCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + onAjaxProgress: (controller, ajaxRequest) async { + if (ajaxRequest.event!.type == AjaxRequestEventType.LOAD) { + Map res = ajaxRequest.response; + onAjaxProgressCompleter.complete(res); + } + return AjaxRequestAction.PROCEED; + }, + ), + ), + ); + + await shouldInterceptAjaxPostRequestCompleter.future; + final Map onAjaxReadyStateChangeValue = + await onAjaxReadyStateChangeCompleter.future; + final Map onAjaxProgressValue = + await onAjaxProgressCompleter.future; + + expect( + mapEquals(onAjaxReadyStateChangeValue, + {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + expect( + mapEquals( + onAjaxProgressValue, {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + }); }); - testWidgets('Content Blocker', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); - final Completer pageLoaded = Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialUrlRequest: - URLRequest(url: Uri.parse('https://flutter.dev/')), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - initialOptions: InAppWebViewGroupOptions( - crossPlatform: - InAppWebViewOptions(clearCache: true, contentBlockers: [ - ContentBlocker( - trigger: - ContentBlockerTrigger(urlFilter: ".*", resourceType: [ - ContentBlockerTriggerResourceType.IMAGE, - ContentBlockerTriggerResourceType.STYLE_SHEET - ], ifTopUrl: [ - "https://flutter.dev/" - ]), - action: ContentBlockerAction( - type: ContentBlockerActionType.BLOCK)) - ])), - onLoadStop: (controller, url) { - pageLoaded.complete(); - }, - ), - ), - ); - await expectLater(pageLoaded.future, completes); - }); - - testWidgets('intercept fetch request', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); - final Completer fetchGetCompleter = Completer(); - final Completer> fetchPostCompleter = - Completer>(); - final Completer shouldInterceptFetchPostRequestCompleter = - Completer(); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialData: InAppWebViewInitialData(data: """ + group('intercept fetch request', () { + testWidgets('send string data', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer fetchGetCompleter = Completer(); + final Completer> fetchPostCompleter = + Completer>(); + final Completer shouldInterceptFetchPostRequestCompleter = + Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialData: InAppWebViewInitialData(data: """ @@ -1859,21 +2090,12 @@ void main() {

InAppWebViewFetchTest

+ + + """), + initialOptions: InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + clearCache: true, + useShouldInterceptFetchRequest: true, + )), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + + controller.addJavaScriptHandler( + handlerName: "fetchPost", + callback: (args) { + fetchPostCompleter + .complete(args[0] as Map); + }); + }, + shouldInterceptFetchRequest: (controller, fetchRequest) async { + expect(fetchRequest.body, '{"firstname":"Foo","lastname":"Bar"}'); + + fetchRequest.body = "{'firstname': 'Foo2', 'lastname': 'Bar2'}"; + shouldInterceptFetchPostRequestCompleter.complete(); + return fetchRequest; + }, + ), + ), + ); + + var fetchGetCompleterValue = await fetchGetCompleter.future; + expect(fetchGetCompleterValue, '200'); + + await shouldInterceptFetchPostRequestCompleter.future; + var fetchPostCompleterValue = await fetchPostCompleter.future; + + expect( + mapEquals(fetchPostCompleterValue, + {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + }); + + testWidgets('send URLSearchParams data', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer fetchGetCompleter = Completer(); + final Completer> fetchPostCompleter = + Completer>(); + final Completer shouldInterceptFetchPostRequestCompleter = + Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialData: InAppWebViewInitialData(data: """ + + + + + + + InAppWebViewFetchTest + + +

InAppWebViewFetchTest

+ + + + """), + initialOptions: InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + clearCache: true, + useShouldInterceptFetchRequest: true, + )), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + + controller.addJavaScriptHandler( + handlerName: "fetchPost", + callback: (args) { + fetchPostCompleter + .complete(args[0] as Map); + }); + }, + shouldInterceptFetchRequest: (controller, fetchRequest) async { + expect(fetchRequest.body, "firstname=Foo&lastname=Bar"); + + fetchRequest.body = "firstname=Foo2&lastname=Bar2"; + shouldInterceptFetchPostRequestCompleter.complete(); + return fetchRequest; + }, + ), + ), + ); + + var fetchGetCompleterValue = await fetchGetCompleter.future; + expect(fetchGetCompleterValue, '200'); + + await shouldInterceptFetchPostRequestCompleter.future; + var fetchPostCompleterValue = await fetchPostCompleter.future; + + expect( + mapEquals(fetchPostCompleterValue, + {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + }); + + testWidgets('send FormData', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer fetchGetCompleter = Completer(); + final Completer> fetchPostCompleter = + Completer>(); + final Completer shouldInterceptFetchPostRequestCompleter = + Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialData: InAppWebViewInitialData(data: """ + + + + + + + InAppWebViewFetchTest + + +

InAppWebViewFetchTest

+ + + + """), + initialOptions: InAppWebViewGroupOptions( + crossPlatform: InAppWebViewOptions( + clearCache: true, + useShouldInterceptFetchRequest: true, + )), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + + controller.addJavaScriptHandler( + handlerName: "fetchPost", + callback: (args) { + fetchPostCompleter + .complete(args[0] as Map); + }); + }, + shouldInterceptFetchRequest: (controller, fetchRequest) async { + expect(fetchRequest.body, isNotNull); + + var body = fetchRequest.body.cast(); + var bodyString = String.fromCharCodes(body); + expect(bodyString.indexOf("WebKitFormBoundary") >= 0, true); + + fetchRequest.body = utf8.encode(bodyString.replaceFirst("Foo", "Foo2").replaceFirst("Bar", "Bar2")); + shouldInterceptFetchPostRequestCompleter.complete(); + return fetchRequest; + }, + ), + ), + ); + + var fetchGetCompleterValue = await fetchGetCompleter.future; + expect(fetchGetCompleterValue, '200'); + + await shouldInterceptFetchPostRequestCompleter.future; + var fetchPostCompleterValue = await fetchPostCompleter.future; + + expect( + mapEquals(fetchPostCompleterValue, + {'firstname': 'Foo2', 'lastname': 'Bar2'}), + true); + }); + }); + + testWidgets('Content Blocker', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer pageLoaded = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialUrlRequest: + URLRequest(url: Uri.parse('https://flutter.dev/')), onWebViewCreated: (controller) { controllerCompleter.complete(controller); - - controller.addJavaScriptHandler( - handlerName: "fetchGet", - callback: (args) { - fetchGetCompleter.complete(args[0].toString()); - }); - - controller.addJavaScriptHandler( - handlerName: "fetchPost", - callback: (args) { - fetchPostCompleter - .complete(args[0] as Map); - }); }, - shouldInterceptFetchRequest: (controller, fetchRequest) async { - if (fetchRequest.url!.toString().endsWith("/test-ajax-post")) { - fetchRequest.body = utf8.encode("""{ - "firstname": "Foo2", - "lastname": "Bar2" - } - """) as Uint8List; - shouldInterceptFetchPostRequestCompleter.complete(); - } - return fetchRequest; + initialOptions: InAppWebViewGroupOptions( + crossPlatform: + InAppWebViewOptions(clearCache: true, contentBlockers: [ + ContentBlocker( + trigger: + ContentBlockerTrigger(urlFilter: ".*", resourceType: [ + ContentBlockerTriggerResourceType.IMAGE, + ContentBlockerTriggerResourceType.STYLE_SHEET + ], ifTopUrl: [ + "https://flutter.dev/" + ]), + action: ContentBlockerAction( + type: ContentBlockerActionType.BLOCK)) + ])), + onLoadStop: (controller, url) { + pageLoaded.complete(); }, ), ), ); - - var fetchGetCompleterValue = await fetchGetCompleter.future; - expect(fetchGetCompleterValue, '200'); - - await shouldInterceptFetchPostRequestCompleter.future; - var fetchPostCompleterValue = await fetchPostCompleter.future; - - expect( - mapEquals(fetchPostCompleterValue, - {'firstname': 'Foo2', 'lastname': 'Bar2'}), - true); + await expectLater(pageLoaded.future, completes); }); testWidgets('Http Auth Credential Database', (WidgetTester tester) async { @@ -5001,4 +5509,37 @@ setTimeout(function() { expect(chromeSafariBrowser.isOpened(), false); }); }); + + group('InAppLocalhostServer', () { + final InAppLocalhostServer localhostServer = InAppLocalhostServer(); + + setUpAll(() async { + localhostServer.start(); + }); + + testWidgets('load asset file', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialUrlRequest: + URLRequest(url: Uri.parse('http://localhost:8080/test_assets/index.html')), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ); + final InAppWebViewController controller = + await controllerCompleter.future; + final String? currentUrl = (await controller.getUrl())?.toString(); + expect(currentUrl, 'http://localhost:8080/test_assets/index.html'); + }); + + tearDownAll(() async { + localhostServer.close(); + }); + }); } diff --git a/ios/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift b/ios/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift index b51ca464..4ec05183 100644 --- a/ios/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift +++ b/ios/Classes/PluginScriptsJS/InterceptAjaxRequestJS.swift @@ -183,46 +183,65 @@ let INTERCEPT_AJAX_REQUEST_JS_SOURCE = """ this.addEventListener('error', handleEvent); this.addEventListener('abort', handleEvent); this.addEventListener('timeout', handleEvent); - var ajaxRequest = { - data: data, - method: this._flutter_inappwebview_method, - url: this._flutter_inappwebview_url, - isAsync: this._flutter_inappwebview_isAsync, - user: this._flutter_inappwebview_user, - password: this._flutter_inappwebview_password, - withCredentials: this.withCredentials, - headers: this._flutter_inappwebview_request_headers, - responseType: this.responseType - }; - window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) { - if (result != null) { - switch (result.action) { - case 0: - self.abort(); - return; + \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(data).then(function(data) { + var ajaxRequest = { + data: data, + method: self._flutter_inappwebview_method, + url: self._flutter_inappwebview_url, + isAsync: self._flutter_inappwebview_isAsync, + user: self._flutter_inappwebview_user, + password: self._flutter_inappwebview_password, + withCredentials: self.withCredentials, + headers: self._flutter_inappwebview_request_headers, + responseType: self.responseType }; - data = result.data; - self.withCredentials = result.withCredentials; - if (result.responseType != null) { - self.responseType = result.responseType; - }; - for (var header in result.headers) { - var value = result.headers[header]; - var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header]; - if (flutter_inappwebview_value == null) { - self._flutter_inappwebview_request_headers[header] = value; - } else { - self._flutter_inappwebview_request_headers[header] += ', ' + value; + window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) { + if (result != null) { + switch (result.action) { + case 0: + self.abort(); + return; + }; + if (result.data != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) && result.data.length > 0) { + var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.data); + if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) { + var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).getFormDataContentType(bodyString); + if (result.headers != null) { + result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type']; + } else { + result.headers = { 'Content-Type': formDataContentType }; + } + } + } + if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) || result.data == null) { + data = result.data; + } else if (result.data.length > 0) { + data = new Uint8Array(result.data); + } + self.withCredentials = result.withCredentials; + if (result.responseType != null) { + self.responseType = result.responseType; + }; + if (result.headers != null) { + for (var header in result.headers) { + var value = result.headers[header]; + var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header]; + if (flutter_inappwebview_value == null) { + self._flutter_inappwebview_request_headers[header] = value; + } else { + self._flutter_inappwebview_request_headers[header] += ', ' + value; + } + setRequestHeader.call(self, header, value); + }; + } + if ((self._flutter_inappwebview_method != result.method && result.method != null) || (self._flutter_inappwebview_url != result.url && result.url != null)) { + self.abort(); + self.open(result.method, result.url, result.isAsync, result.user, result.password); + return; + } } - setRequestHeader.call(self, header, value); - }; - if ((self._flutter_inappwebview_method != result.method && result.method != null) || (self._flutter_inappwebview_url != result.url && result.url != null)) { - self.abort(); - self.open(result.method, result.url, result.isAsync, result.user, result.password); - return; - } - } - send.call(self, data); + send.call(self, data); + }); }); } else { send.call(this, data); diff --git a/ios/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift b/ios/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift index 3fa45c3b..14539811 100644 --- a/ios/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift +++ b/ios/Classes/PluginScriptsJS/InterceptFetchRequestJS.swift @@ -24,68 +24,6 @@ let INTERCEPT_FETCH_REQUEST_JS_SOURCE = """ if (fetch == null) { return; } - function convertHeadersToJson(headers) { - var headersObj = {}; - for (var header of headers.keys()) { - var value = headers.get(header); - headersObj[header] = value; - } - return headersObj; - } - function convertJsonToHeaders(headersJson) { - return new Headers(headersJson); - } - function convertBodyToArray(body) { - return new Response(body).arrayBuffer().then(function(arrayBuffer) { - var arr = Array.from(new Uint8Array(arrayBuffer)); - return arr; - }) - } - function convertArrayIntBodyToUint8Array(arrayIntBody) { - return new Uint8Array(arrayIntBody); - } - function convertCredentialsToJson(credentials) { - var credentialsObj = {}; - if (window.FederatedCredential != null && credentials instanceof FederatedCredential) { - credentialsObj.type = credentials.type; - credentialsObj.id = credentials.id; - credentialsObj.name = credentials.name; - credentialsObj.protocol = credentials.protocol; - credentialsObj.provider = credentials.provider; - credentialsObj.iconURL = credentials.iconURL; - } else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) { - credentialsObj.type = credentials.type; - credentialsObj.id = credentials.id; - credentialsObj.name = credentials.name; - credentialsObj.password = credentials.password; - credentialsObj.iconURL = credentials.iconURL; - } else { - credentialsObj.type = 'default'; - credentialsObj.value = credentials; - } - } - function convertJsonToCredential(credentialsJson) { - var credentials; - if (window.FederatedCredential != null && credentialsJson.type === 'federated') { - credentials = new FederatedCredential({ - id: credentialsJson.id, - name: credentialsJson.name, - protocol: credentialsJson.protocol, - provider: credentialsJson.provider, - iconURL: credentialsJson.iconURL - }); - } else if (window.PasswordCredential != null && credentialsJson.type === 'password') { - credentials = new PasswordCredential({ - id: credentialsJson.id, - name: credentialsJson.name, - password: credentialsJson.password, - iconURL: credentialsJson.iconURL - }); - } else { - credentials = credentialsJson; - } - return credentials; - } window.fetch = async function(resource, init) { if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == true) { var fetchRequest = { @@ -116,7 +54,7 @@ let INTERCEPT_FETCH_REQUEST_JS_SOURCE = """ fetchRequest.integrity = resource.integrity; fetchRequest.keepalive = resource.keepalive; } else { - fetchRequest.url = resource; + fetchRequest.url = resource != null ? resource.toString() : null; if (init != null) { fetchRequest.method = init.method; fetchRequest.headers = init.headers; @@ -132,10 +70,10 @@ let INTERCEPT_FETCH_REQUEST_JS_SOURCE = """ } } if (fetchRequest.headers instanceof Headers) { - fetchRequest.headers = convertHeadersToJson(fetchRequest.headers); + fetchRequest.headers = \(JAVASCRIPT_UTIL_VAR_NAME).convertHeadersToJson(fetchRequest.headers); } - fetchRequest.credentials = convertCredentialsToJson(fetchRequest.credentials); - return convertBodyToArray(fetchRequest.body).then(function(body) { + fetchRequest.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertCredentialsToJson(fetchRequest.credentials); + return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(fetchRequest.body).then(function(body) { fetchRequest.body = body; return window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) { if (result != null) { @@ -152,7 +90,18 @@ let INTERCEPT_FETCH_REQUEST_JS_SOURCE = """ controller.abort(); break; } - resource = (result.url != null) ? result.url : resource; + if (result.body != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) && result.body.length > 0) { + var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.body); + if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) { + var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).getFormDataContentType(bodyString); + if (result.headers != null) { + result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type']; + } else { + result.headers = { 'Content-Type': formDataContentType }; + } + } + } + resource = result.url; if (init == null) { init = {}; } @@ -160,16 +109,18 @@ let INTERCEPT_FETCH_REQUEST_JS_SOURCE = """ init.method = result.method; } if (result.headers != null && Object.keys(result.headers).length > 0) { - init.headers = convertJsonToHeaders(result.headers); + init.headers = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToHeaders(result.headers); } - if (result.body != null && result.body.length > 0) { - init.body = convertArrayIntBodyToUint8Array(result.body); + if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) || result.body == null) { + init.body = result.body; + } else if (result.body.length > 0) { + init.body = new Uint8Array(result.body); } if (result.mode != null && result.mode.length > 0) { init.mode = result.mode; } if (result.credentials != null) { - init.credentials = convertJsonToCredential(result.credentials); + init.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToCredential(result.credentials); } if (result.cache != null && result.cache.length > 0) { init.cache = result.cache; diff --git a/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift b/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift index 2d4b7f89..9aa6334d 100644 --- a/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift +++ b/ios/Classes/PluginScriptsJS/JavaScriptBridgeJS.swift @@ -30,6 +30,213 @@ window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() { }); }; \(WEB_MESSAGE_LISTENER_JS_SOURCE) +\(UTIL_JS_SOURCE) """ let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));"; + +let JAVASCRIPT_UTIL_VAR_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._Util" + +/* + https://github.com/github/fetch/blob/master/fetch.js + */ +let UTIL_JS_SOURCE = """ +\(JAVASCRIPT_UTIL_VAR_NAME) = { + support: { + searchParams: 'URLSearchParams' in window, + iterable: 'Symbol' in window && 'iterator' in Symbol, + blob: + 'FileReader' in window && + 'Blob' in window && + (function() { + try { + new Blob(); + return true; + } catch (e) { + return false; + } + })(), + formData: 'FormData' in window, + arrayBuffer: 'ArrayBuffer' in window + }, + isDataView: function(obj) { + return obj && DataView.prototype.isPrototypeOf(obj); + }, + fileReaderReady: function(reader) { + return new Promise(function(resolve, reject) { + reader.onload = function() { + resolve(reader.result); + }; + reader.onerror = function() { + reject(reader.error); + }; + }); + }, + readBlobAsArrayBuffer: function(blob) { + var reader = new FileReader(); + var promise = \(JAVASCRIPT_UTIL_VAR_NAME).fileReaderReady(reader); + reader.readAsArrayBuffer(blob); + return promise; + }, + convertBodyToArrayBuffer: function(body) { + var viewClasses = [ + '[object Int8Array]', + '[object Uint8Array]', + '[object Uint8ClampedArray]', + '[object Int16Array]', + '[object Uint16Array]', + '[object Int32Array]', + '[object Uint32Array]', + '[object Float32Array]', + '[object Float64Array]' + ]; + var isArrayBufferView = null; + if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer) { + isArrayBufferView = + ArrayBuffer.isView || + function(obj) { + return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1; + }; + } + + var bodyUsed = false; + + this._bodyInit = body; + if (!body) { + this._bodyText = ''; + } else if (typeof body === 'string') { + this._bodyText = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.blob && Blob.prototype.isPrototypeOf(body)) { + this._bodyBlob = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.formData && FormData.prototype.isPrototypeOf(body)) { + this._bodyFormData = body; + } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { + this._bodyText = body.toString(); + } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && \(JAVASCRIPT_UTIL_VAR_NAME).support.blob && \(JAVASCRIPT_UTIL_VAR_NAME).isDataView(body)) { + this._bodyArrayBuffer = bufferClone(body.buffer); + this._bodyInit = new Blob([this._bodyArrayBuffer]); + } else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { + this._bodyArrayBuffer = bufferClone(body); + } else { + this._bodyText = body = Object.prototype.toString.call(body); + } + + this.blob = function () { + if (bodyUsed) { + return Promise.reject(new TypeError('Already read')); + } + bodyUsed = true; + if (this._bodyBlob) { + return Promise.resolve(this._bodyBlob); + } else if (this._bodyArrayBuffer) { + return Promise.resolve(new Blob([this._bodyArrayBuffer])); + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as blob'); + } else { + return Promise.resolve(new Blob([this._bodyText])); + } + }; + + if (this._bodyArrayBuffer) { + if (bodyUsed) { + return Promise.reject(new TypeError('Already read')); + } + bodyUsed = true; + if (ArrayBuffer.isView(this._bodyArrayBuffer)) { + return Promise.resolve( + this._bodyArrayBuffer.buffer.slice( + this._bodyArrayBuffer.byteOffset, + this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength + ) + ); + } else { + return Promise.resolve(this._bodyArrayBuffer); + } + } + return this.blob().then(\(JAVASCRIPT_UTIL_VAR_NAME).readBlobAsArrayBuffer); + }, + isString: function(variable) { + return typeof variable === 'string' || variable instanceof String; + }, + convertBodyRequest: function(body) { + if (body == null) { + return new Promise((resolve, reject) => resolve(null)); + } + if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(body) || (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && body instanceof URLSearchParams)) { + return new Promise((resolve, reject) => resolve(body.toString())); + } + if (window.Response != null) { + return new Response(body).arrayBuffer().then(function(arrayBuffer) { + return Array.from(new Uint8Array(arrayBuffer)); + }); + } + return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyToArrayBuffer(body).then(function(arrayBuffer) { + return Array.from(new Uint8Array(arrayBuffer)); + }); + }, + arrayBufferToString: function(arrayBuffer) { + return String.fromCharCode.apply(String, arrayBuffer); + }, + isBodyFormData: function(bodyString) { + return bodyString.indexOf('------WebKitFormBoundary') >= 0; + }, + getFormDataContentType: function(bodyString) { + var boundary = bodyString.substr(2, 40); + return 'multipart/form-data; boundary=' + boundary; + }, + convertHeadersToJson: function(headers) { + var headersObj = {}; + for (var header of headers.keys()) { + var value = headers.get(header); + headersObj[header] = value; + } + return headersObj; + }, + convertJsonToHeaders: function(headersJson) { + return new Headers(headersJson); + }, + convertCredentialsToJson: function(credentials) { + var credentialsObj = {}; + if (window.FederatedCredential != null && credentials instanceof FederatedCredential) { + credentialsObj.type = credentials.type; + credentialsObj.id = credentials.id; + credentialsObj.name = credentials.name; + credentialsObj.protocol = credentials.protocol; + credentialsObj.provider = credentials.provider; + credentialsObj.iconURL = credentials.iconURL; + } else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) { + credentialsObj.type = credentials.type; + credentialsObj.id = credentials.id; + credentialsObj.name = credentials.name; + credentialsObj.password = credentials.password; + credentialsObj.iconURL = credentials.iconURL; + } else { + credentialsObj.type = 'default'; + credentialsObj.value = credentials; + } + return credentialsObj; + }, + convertJsonToCredential: function(credentialsJson) { + var credentials; + if (window.FederatedCredential != null && credentialsJson.type === 'federated') { + credentials = new FederatedCredential({ + id: credentialsJson.id, + name: credentialsJson.name, + protocol: credentialsJson.protocol, + provider: credentialsJson.provider, + iconURL: credentialsJson.iconURL + }); + } else if (window.PasswordCredential != null && credentialsJson.type === 'password') { + credentials = new PasswordCredential({ + id: credentialsJson.id, + name: credentialsJson.name, + password: credentialsJson.password, + iconURL: credentialsJson.iconURL + }); + } else { + credentials = credentialsJson.value == null ? undefined : credentialsJson.value; + } + return credentials; + } +}; +""" diff --git a/lib/src/in_app_localhost_server.dart b/lib/src/in_app_localhost_server.dart index 83188744..03593121 100755 --- a/lib/src/in_app_localhost_server.dart +++ b/lib/src/in_app_localhost_server.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/services.dart' show rootBundle; @@ -39,7 +40,8 @@ class InAppLocalhostServer { this._server = server; server.listen((HttpRequest request) async { - var body = [] as List; + Uint8List body = Uint8List(0); + var path = request.requestedUri.path; path = (path.startsWith('/')) ? path.substring(1) : path; path += (path.endsWith('/')) ? 'index.html' : ''; diff --git a/lib/src/types.dart b/lib/src/types.dart index 2cd130a0..f35752ab 100755 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -2873,7 +2873,7 @@ class AjaxRequestHeaders { ///Class that represents a JavaScript [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) object. class AjaxRequest { ///Data passed as a parameter to the `XMLHttpRequest.send()` method. - dynamic data; + dynamic? data; ///The HTTP request method of the `XMLHttpRequest` request. String? method; @@ -3225,7 +3225,7 @@ class FetchRequest { Map? headers; ///Body of the request. - Uint8List? body; + dynamic? body; ///The mode used by the request. String? mode; @@ -3291,9 +3291,7 @@ class FetchRequest { url: map["url"] != null ? Uri.parse(map["url"]) : null, method: map["method"], headers: map["headers"]?.cast(), - body: map["body"] != null - ? Uint8List.fromList(map["body"].cast()) - : null, + body: map["body"], mode: map["mode"], credentials: credentials, cache: map["cache"], diff --git a/nodejs_server_test_auth_basic_and_ssl/index.js b/nodejs_server_test_auth_basic_and_ssl/index.js index 87ac1ef7..f6bcdb16 100755 --- a/nodejs_server_test_auth_basic_and_ssl/index.js +++ b/nodejs_server_test_auth_basic_and_ssl/index.js @@ -9,6 +9,8 @@ const appHttps = express() const appAuthBasic = express() const fs = require('fs') const path = require('path') +const bodyParser = require('body-parser'); +const multiparty = require('multiparty'); var options = { key: fs.readFileSync('server-key.pem'), @@ -117,14 +119,11 @@ appAuthBasic.get('/test-index', (req, res) => { appAuthBasic.listen(8081); - -// Parse URL-encoded bodies (as sent by HTML forms) -app.use(express.urlencoded()); - app.use(cors()); +app.use(bodyParser.urlencoded({extended: false})); // Parse JSON bodies (as sent by API clients) -app.use(express.json()); +app.use(bodyParser.json()); app.use(express.static(__dirname + '/public')); @@ -162,14 +161,27 @@ app.post("/test-post", (req, res) => { }) app.post("/test-ajax-post", (req, res) => { - console.log(JSON.stringify(req.headers)) - console.log(JSON.stringify(req.body)) - res.set("Content-Type", "application/json") - res.send(JSON.stringify({ - "firstname": req.body.firstname, - "lastname": req.body.lastname, - })) - res.end() + console.log(JSON.stringify(req.headers)); + if (req.headers["content-type"].indexOf("multipart/form-data;") === 0) { + const form = new multiparty.Form(); + form.parse(req, function(err, fields, files) { + console.log(fields); + res.set("Content-Type", "application/json") + res.send(JSON.stringify({ + "firstname": fields.firstname[0], + "lastname": fields.lastname[0], + })); + res.end(); + }); + } else { + console.log(req.body); + res.set("Content-Type", "application/json") + res.send(JSON.stringify({ + "firstname": req.body.firstname, + "lastname": req.body.lastname, + })); + res.end(); + } }) app.get("/test-download-file", (req, res) => { @@ -184,4 +196,4 @@ app.get("/test-download-file", (req, res) => { res.end(); }) -app.listen(8082) \ No newline at end of file +app.listen(8082) diff --git a/nodejs_server_test_auth_basic_and_ssl/package-lock.json b/nodejs_server_test_auth_basic_and_ssl/package-lock.json index 63329d22..04f0a5dc 100755 --- a/nodejs_server_test_auth_basic_and_ssl/package-lock.json +++ b/nodejs_server_test_auth_basic_and_ssl/package-lock.json @@ -252,6 +252,45 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "multiparty": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.2.tgz", + "integrity": "sha512-NtZLjlvsjcoGrzojtwQwn/Tm90aWJ6XXtPppYF4WmOk/6ncdwMMKggFY2NlRRN9yiCEIVxpOfPWahVEG2HAG8Q==", + "requires": { + "http-errors": "~1.8.0", + "safe-buffer": "5.2.1", + "uid-safe": "2.1.5" + }, + "dependencies": { + "http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + } + } + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -294,6 +333,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -382,6 +426,14 @@ "mime-types": "~2.1.24" } }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/nodejs_server_test_auth_basic_and_ssl/package.json b/nodejs_server_test_auth_basic_and_ssl/package.json index 049f42cf..6feac6cc 100755 --- a/nodejs_server_test_auth_basic_and_ssl/package.json +++ b/nodejs_server_test_auth_basic_and_ssl/package.json @@ -10,8 +10,10 @@ "license": "ISC", "dependencies": { "basic-auth": "latest", + "body-parser": "^1.19.0", "cors": "^2.8.5", "express": "latest", - "https": "latest" + "https": "latest", + "multiparty": "^4.2.2" } }