How to upload an image to AWS S3 pre-assign URL with background-http.
Tags: nativescript, typescript, aws, s3
Published: 2022-12-04 21:53:18

Recently I had a task to upload an image to an s3 pre-assign URL in my project using the framework Nativescript.

## So what was the challenge? 
Send an `ImageSource` using the `background-http`.

My first attempts weren't successful because I was trying to send the base64 data or even the ImageSource himself which at the time seemed a nice idea but wasn't. All the data were corrupted or not even sent. 
So after some attempts and a few hours of trying and failing, I finally got the right way to do it.
I must thank the guys from Nativescript, wwwalkerrun and triniwiz, for guiding me in the right way.

## Installing the Packages
In order to upload data to the S3 we need [background-http](https://www.npmjs.com/package/@nativescript/background-http) to help us here.
```console
ns plugin add @nativescript/background-http
```
If you want to check the plugin you can do it [here](https://www.npmjs.com/package/@nativescript/background-http).

In your main.ts or main.js or where your application starts you need to initialise the background plugin. To do so

```typescript
import { init } from '@nativescript/background-http';
init();
```
We are importing the init function and initialising it in that application start file.

## Creating the AWS Service
To make my code clean I create a file called `AWSService.ts` which will simplify the way we will use the service.

```ts
import { Http, HttpRequestOptions, HttpResponse } from '@nativescript/core';
import { Request, Session } from '@nativescript/background-http';
import { HttpService } from '~/services/generic/Http.service';

export class AWSService {
    public get session(): Session {
        return this._session;
    }
    public set session(value: Session) {
        this._session = value;
    }

    private _session: Session;

    constructor() {
        this.session = this.initSession();
    }
    initSession(): Session {
        return bghttp.session('image-upload'); // you can rename this image-upload to watever you like.
    }

    async requestUrl(name: string): Promise<HttpResponse> {
// This will be your implementation to request the pre-assigned url
    }

    async uploadImage(url: string, file: any, name?: string, type?: string): Promise<string> {
        try {
            if (!type) type = 'image/jpg';
            if (!name) name = 'filename.jpg';
            const request: Request = {
                url,
                method: 'PUT',
                headers: {
                    'Content-Type': type,
                    reportProgress: true,
                },
                description: 'UpladingFile' + file,
            };
            this.initTask(file, request, name, type);
        } catch (e) {
            console.log(e);
            return '';
        }
    }
    initTask(file: any, request: Request, name: string, type: string): void {
        try {
            const params = [{ name, filename: file, mimeType: type }];
            const task = this.session.uploadFile(file, request);
            task.on('progress', e => console.log('progress', e.currentBytes));
            task.on('responded', e => console.log('responded', e.responseCode));
            task.on('error', e => console.log('error', e.responseCode));
            task.on('complete', e => {
                console.log('complete', e);
                const toast = new Toasty({
                    text: 'Image uploaded',
                    position: ToastPosition.BOTTOM,
                });
                toast.duration = ToastDuration.SHORT;
                toast.show();
            });
        } catch (e) {
            console.log('initTaks Erro -', e);
        }
    }
}
```

Breaking the code in parts let's start with the `initSession`
We are creating a new session when we create a new instance of AWSService. And the `initSession()` creates a new session of bghttp.

```ts
constructor(){
   this.session = this.initSession();
}
```
```ts
initSession(): Session {
        return bghttp.session('some name);
    }
```

The `uploadImage` function takes the pre-assign URL, the path of the file, the name of the file and the mimeType. We grab those create a Request object and init the task.

```ts
async sendImage(url: string, file: any, name?: string, type?: string): Promise<string> {
        try {
            if (!type) type = 'image/jpg';
            if (!name) name = 'filename.jpg';
            const request: Request = {
                url,
                method: 'PUT',
                headers: {
                    'Content-Type': type,
                    reportProgress: true,
                },
                description: 'UpladingFile' + file,
            };
            this.initTask(file, request, name, type);
        } catch (e) {
            console.log(e);
            return '';
        }
    }
```

And last but not least our `initTask` function initialise our upload process. This takes our file path and the request created by the `sendImage` function. I add the events from the task but I'm not doing anything right now with him, but you can track the progress, the result and if some error occurs. If everything works perfectly your `respond` or `complete` will return a result. To be sure navigate to your AWS s3 bucket and check if the file is not corrupted. If not Good Job your service is running perfectly.

```ts
initTask(file: any, request: Request): void {
        try {
            const task = this.session.uploadFile(file, request);
            task.on('progress', e => console.log('progress', e.currentBytes));
            task.on('responded', e => console.log('responded', e.responseCode));
            task.on('error', e => console.log('error', e.responseCode));
            task.on('complete', e => console.log('complete', e));
        } catch (e) {
            console.log('initTaks Erro -', e);
        }
    }
```

## Using the AWSService

Using the service is really simple you just need to initialise and call the functions. Of course we need the image source the example below is using Image Data from another package that let you draw a paint.

```ts
...
const amazonService = new AWSService();
// Grab the preassign url
const preAssignURL = await amazonService.requestUrl('image.png');
// data returned from the draw paint
const data: UIImage | android.graphics.Bitmap = await draw.getDrawing();
// Creating a new ImageSource
const source = new ImageSource(newImage);
const path = `${knownFolders.documents().path}/image.png`;
// Saving the image source to a file;
source.saveToFile(path, 'png');
await amazonService.sendImage(preAssignURL,path, 'image/png');
```

If you don't want the keep the file in the device you can simply delete by adding the follow code

```ts
const file = File.fromPath(path);
file.remove();
```

That's it. If your file is in the S3 bucket and is not corrupted everything works as we want.

If you want the check the full code you can go to my [gist](https://gist.github.com/vanishdark/884c3a01bab6e5c4e4e52611b40a68de) or if you prefer here is the individual files
- [AWSService.ts](https://gist.github.com/vanishdark/884c3a01bab6e5c4e4e52611b40a68de#file-awsservice-ts)
- [example.ts](https://gist.github.com/vanishdark/884c3a01bab6e5c4e4e52611b40a68de#file-b-ts)
- [HttpService - bonus file](https://gist.github.com/vanishdark/884c3a01bab6e5c4e4e52611b40a68de#file-httpservice-ts)