NativeScript / nativescript-background-http

Background Upload plugin for the NativeScript framework
Apache License 2.0
102 stars 50 forks source link

Uploading image for iOS returns responseCode -1 and response null #214

Closed nickdekkers closed 5 years ago

nickdekkers commented 5 years ago

Make sure to check the demo app(s) for sample usage

The demo app does not cover this problem

Make sure to check the existing issues in this repository

No related issues

If the demo apps cannot help and there is no issue for your problem, tell us about it

When I want to upload an image on an iOS simulator the plugin, in the complete method, returns a responseCode -1 and a response null. Whereas on Android the same functionality returns a responseCode 200 and a response {}. So on Android it is working fine, but on iOS it returns an error.

Uploading an image on an iOS simulator will return the following error:

 null is not an object (evaluating 'result.response.allHeaderFields')

After investigation it turns out that the background-http.ios.js file has an onError function in which it will return a responseCode of -1 and a response of null.

task.notify({
            eventName: "complete",
            object: task,
            responseCode: response ? response.statusCode : -1,
            response: response
        });

The response for some reason is null, which happens because earlier in this same function

var response = nsTask && nsTask.response ? nsTask.response : null;

sets the response to null.

My question is: How come either the nsTask or the nsTask.response is undefined?

Which platform(s) does your issue occur on?

iOS simulator

Please, provide the following version numbers that your issue occurs with:

Please, tell us how to recreate the issue in as much detail as possible.

Create a simple app where you use the imagepicker to retrieve an image from the iOS simulator and upload that image using this plugin.

Is there any code involved?

upload(apiUrl: string, filepath: string, filename: string, progressSubscriber?: Subscriber<IFileUploadProgress>): Observable<HttpResponse<any>> {
        apiUrl = this.apiUrlAppender.appendUrl(apiUrl);

        return new Observable<HttpResponse<any>>(subscriber => {
            const session = bghttp.session("image-upload");
            const request = <Request> {
                url: apiUrl,
                method: "POST",
                headers: {
                    "Content-Type": "application/octet-stream",
                    "Authorization": this.authorizationContext.getToken().getHeaderValue()
                },
                description: ''
            };

            const params = [
                { name: filename, filename: filepath }
            ];

            const task = session.multipartUpload(params, request);

            if (progressSubscriber) {
                task.on('progress', (result: any) => {
                    this.zone.run(() => {
                        progressSubscriber.next(result);
                    });
                });
            }

            task.on("error", (result) => {
                this.zone.run(() => {
                    subscriber.error(result.error);
                })
            });

            let iosResponse: string;

            if (isIOS) {
                task.on('responded', (result) => {
                    if (result.responseCode >= 300) {
                        subscriber.error("Got response code: " + result.responseCode);
                    }
                    iosResponse = result.data;
                });
            }

            task.on('complete', (result: any) => {
                if (subscriber.closed) {
                    return;
                }

                let headers = new HttpHeaders();

                if (isAndroid) {
                    const iterator = result.response.getHeaders().entrySet().iterator();
                    while (iterator.hasNext()) {
                        const header = iterator.next();
                        headers = headers.append(header.getKey, header.getValue());
                    }
                } else if (isIOS) {
                    console.dir(result.response.allHeaderFields);
                }

                const response = new HttpResponse({
                    body: JSON.parse(iosResponse || result.response.getBodyAsString()),
                    headers,
                    status: result.responseCode,
                    url: apiUrl
                });

                this.zone.run(() => {
                    if (progressSubscriber) {
                        progressSubscriber.complete();
                    }

                    if (result.responseCode == 200) {
                        subscriber.next(response);
                    } else {
                        subscriber.error(response);
                    }

                    subscriber.complete();
                });
            })
        })
        .pipe(map(x => x.body))
    }
nickdekkers commented 5 years ago

@NickIliev added more information to the issue report

Melf11 commented 5 years ago

i got the same issue with mostly the same versions

mikaelkalt commented 5 years ago

I'm facing the same problem. The weirdest part of it is that we have two different places in the application which use the http-background services and one of them is still working and the other one is getting the responseCode -1 as well... The broken one uploads a ZIP file if it matters.

It is also failing on the device itself - so clearly not related to the simulator.

Edit:

The underlying error I can see on the console looks like this:

Task <4B8832F2-6A42-4473-8EA6-3F95FC2417C0>.<1> load failed with error Error Domain=NSPOSIXErrorDomain Code=2 UserInfo={NSErrorFailingURLStringKey=<private>, NSErrorFailingURLKey=<private>, _NSURLErrorRelatedURLSessionTaskErrorKey=<private>, _NSURLErrorFailingURLSessionTaskErrorKey=<private>} [2]
CONSOLE ERROR file:///app/bundle.js:4527:29: ERROR: HandleErrorEvent: Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory" UserInfo={NSErrorFailingURLStringKey=<OUR_URL> NSErrorFailingURLKey=<OUR_URL>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"BackgroundUploadTask <4B8832F2-6A42-4473-8EA6-3F95FC2417C0>.<1>",
"LocalDownloadTask <4B8832F2-6A42-4473-8EA6-3F95FC2417C0>.<1>"
), _NSURLErrorFailingURLSessionTaskErrorKey=BackgroundUploadTask <4B8832F2-6A42-4473-8EA6-3F95FC2417C0>.<1>}/-1
TheoGros commented 5 years ago

I'm having the same issue on Android as well. The upload is not working anymore since I updated NS to 5.3.1.

mikaelkalt commented 5 years ago

Since this issue is currently really a blocker for us, I've created a simple basic example which reproduces the problem on iOS:

Playground: https://play.nativescript.org/?template=play-ng&id=ntfHTV&v=2

I have tested it with Android, the upload is successful - return code 200. On iOS I get an responseCode -1 as all the others.

@NickIliev is there anything else I could help with or you need to know?

alexiscoelho commented 5 years ago

Same issue here, works fine on Android but on iOS I just receive a a -1 response uploading an image. Getting crazy with this...

madmas commented 5 years ago

Experiencing the very same issue. @NickIliev, @elena-p @DimitarTachev (adding you as you merged PR recently) - can we add more information on this to help? Or do you have a tip what to investigate to carve out a PR, maybe?

Melf11 commented 5 years ago

I'm having the same issue on Android as well. The upload is not working anymore since I updated NS to 5.3.1.

i got the same with android if there is some error response from API incomming, with 200 it is okay. On iOS i got still the problem even if the file upload is completed i will get -1. The file is uploaded successful but i can get a success dialog to my app. Even the completeHandler doesn't work.

NickIliev commented 5 years ago

@Melf11 @madmas @alexiscoelho @nickdekkers @mikaelkalt @Melf11 @TheoGros

The thing is that the responseCode is actually not supported by the plugin for iOS as commented in the typescript definition. Marking the issue as a feature request.

/** HTTP response code if response object is present, otherwise -1 */
responseCode: number;
/** Currently available for Android only */
response?: any; // net.gotev.uploadservice.ServerResponse

Anyone interested in the implementation is welcomed as the plugin is open source and we are accepting PRs.

mikaelkalt commented 5 years ago

@NickIliev Thanks for your reply. That might be a stupid question, but how can I figure out that my upload was successful on iOS? As you can see in my playground example, the plugin jumps into the error callback. That's actually in my opinion a bug - because the upload is successful.

NickIliev commented 5 years ago

@mikaelkalt using your Playground I am entering the completeHandler - changed the code a little bit to make a difference in the outputted info.

Melf11 commented 5 years ago

@NickIliev Thanks for your reply. That might be a stupid question, but how can I figure out that my upload was successful on iOS? As you can see in my playground example, the plugin jumps into the error callback. That's actually in my opinion a bug - because the upload is successful.

it's confusing, because i always got the completeHandler fired. It does not make a difference if there is an error or not. Even if i choose a random API Url which is definitively wrong. I wrote a workaround, but now after all the updates i can't get the responseCode to check if it was an error or not

`

errorHandler(e) {

    console.log('error responsecode: ' + JSON.stringify(e));

    if (isIOS) {
        setTimeout(() => {
            dialogs.confirm({
                title: this.translate.instant('share_tab_debug_dialog_error_title'),
                message: this.translate.instant('share_tab_debug_dialog_error_retry'),
                okButtonText: this.translate.instant('confirm_close'),
            }).then(function (result) {
              console.log('dialog result: ' + JSON.stringify(result));
            });
        }, 1000);

    } else {
        dialogs.confirm({
            title: this.translate.instant('share_tab_debug_dialog_error_title'),
            message: this.translate.instant('share_tab_debug_dialog_error_retry'),
            okButtonText: this.translate.instant('confirm_close'),
        }).then(function (result) {
            console.log('dialog result: ' + JSON.stringify(result));
        });
    }

    this.params.closeCallback({message: 'error', response: e});
}

completeHandler(e) {

    console.log('complete responsecode: ' + JSON.stringify(e));

    if (isIOS) {
        // iOS does not fire errorHandler, so do it manually
        if (e.responseCode === 200) {
            // Timeout needed for iOS, otherwise the dialog won't show up
            setTimeout(() => {
                dialogs.confirm({
                    title: this.translate.instant('share_tab_debug_dialog_success_title'),
                    message: this.translate.instant('share_tab_debug_dialog_success'),
                    okButtonText: this.translate.instant('confirm_close'),
                }).then(function (result) {
                    console.log("Dialog result: " + result);
                });
            }, 1000);
        } else {
            this.errorHandler(e);
        }

    } else {
        dialogs.confirm({
            title: this.translate.instant('share_tab_debug_dialog_success_title'),
            message: this.translate.instant('share_tab_debug_dialog_success'),
            okButtonText: this.translate.instant('confirm_close'),
        }).then(function (result) {
            console.log("Dialog result: " + result);
        });
        this.params.closeCallback({message: 'success', response: e});
    }

}`

of course, its not really a nice workaround but it was okay util the will come a better one

mikaelkalt commented 5 years ago

@NickIliev Thanks for the hint.. yeah that demo was not really useful. Sorry for that.

I've tried to build a playground that reproduces our problem but I failed since the nativescript-zip does not work with the playground somehow. Anyways, I took the demo app from the repo here and managed to manipulate it so that it is reproducible.

demo-angular.zip

I've used nativescript-cli 5.3.4 to build run the app with: tns run ios

I could figure out that there must be some relation to the zip file, but I couldn't figure out what's the problem there. Do you have any idea? suggestion?

One very important thing to mention, besides the plugin jumps into the error callback, the file is successfully uploaded to our server if I switch the URL

DimitarTodorov commented 5 years ago

The issue is fixed in https://github.com/NativeScript/nativescript-background-http/releases/tag/v3.4.1 which is now published to npm.

mikaelkalt commented 5 years ago

Interestingly I get now an error with status code 200 instead of -1... which is an improvement :-)

But I'm stilling struggling with the following error:

 Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory" UserInfo={NSErrorFailingURLStringKey=http://10.100.100.65:8080/app-logging-report/CFB5B12C-7171-4801-82D3-C72EB73926E7, NSErrorFailingURLKey=http://10.100.100.65:8080/app-logging-report/CFB5B12C-7171-4801-82D3-C72EB73926E7, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"BackgroundUploadTask <7DBFD033-D52D-49AD-8027-4A81649DACD2>.<1>",
"LocalDownloadTask <7DBFD033-D52D-49AD-8027-4A81649DACD2>.<1>"

I can at least catch it now and check for the status code, which is 200 (success), but it seems to me that there is still bug? Unfortunately I haven't yet managed to reproduce it in a playground project.