Skip to content

Latest commit

 

History

History
832 lines (654 loc) · 20.5 KB

README.md

File metadata and controls

832 lines (654 loc) · 20.5 KB

npm npm

NativeScript API Client

A NativeScript module for simply calling HTTP based APIs.

Donate

NativeScript Toolbox

This module is part of nativescript-toolbox.

License

MIT license

Platforms

  • Android
  • iOS

Installation

Run

tns plugin add nativescript-apiclient

inside your app project to install the module.

Demo

For quick start have a look at the plugin/index.ts or use the "IntelliSense" of your IDE to learn how it works.

Otherwise...

Usage

Import

import ApiClient = require("nativescript-apiclient");

Example

import ApiClient = require("nativescript-apiclient");
import HTTP = require("http");

interface IUser {
    displayName?: string;
    id?: number;
    name?: string;
}

var client = ApiClient.newClient({
    baseUrl: "https://api.example.com/users",
    route: "{id}",  
});

client.beforeSend(function(opts: HTTP.HttpRequestOptions, tag: any) {
                      console.log("Loading user: " + tag);
                      
                      // prepare the request here
                  })
      .clientError(function(result: ApiClient.IApiClientResult) {
                       // handle all responses with status code 400 to 499
                   })
      .serverError(function(result: ApiClient.IApiClientResult) {
                       // handle all responses with status code 500 to 599
                   })
      .success(function(result: ApiClient.IApiClientResult) {
                    // handle all responses with that were NOT
                    // handled by 'clientError()' and 'serverError()'
                    // 
                    // especially with status code less than 400 and greater than 599
                    
                    var user = result.getJSON<IUser>();
               })
      .error(function(err: ApiClient.IApiClientError) {
                 // handle API client errors
             })
      .completed(function(ctx: ApiClient.IApiClientCompleteContext) {
                     // invoked after "result" and "error" actions
                 });

var credentials = new ApiClient.BasicAuth("Marcel", "p@ssword!");

for (var userId = 1; userId <= 100; userId++) {
    // start a GET request
    // 
    // [GET]  https://api.example.com/users/{id}?ver=1.6.6.6
    client.get({
        authorizer: credentials,
    
        // request headers
        headers: {
            'X-MyHeader-TM': '5979',
            'X-MyHeader-MK': '23979'
        },
        
        // URL parameters
        params: {
            ver: '1.6.6.6'
        },
    
        // route parameters
        routeParams: {
            id: userId.toString()  // {id}
        },
        
        // global value for all callbacks 
        tag: userId
    });
}

Routes

Routes are suffixes for a base URL.

You can define one or parameters inside that route, which are replaced when you start a request.

If you create a client like this

var client = ApiClient.newClient({
    baseUrl: "https://api.example.com/users",
    route: "{id}/{resource}",  
});

and start a request like this

client.get({
    routeParams: {
        id: "5979",  // {id}
        resource: "profile"  // {resource}
    }
});

the client will call the URL

[GET]  https://api.example.com/users/5979/profile

Parameter values can also be functions, what means that the value that is returned by that functions is used as value:

var getUserId = function() : string {
    // load the user ID from somewhere
};

client.get({
    routeParams: {
        id: getUserId,  // {id}
        resource: "profile"  // {resource}
    }
});

A function must have the following structure:

function (paramName: string, routeParams: any, match: string, formatExpr: string, funcDepth: string) : any {
    return <THE-VALUE-TO-USE>;
}
Name Description
paramName The name of the parameter. For {id} this will be id
routeParams The list of submitted route parameters with their values. IMPORTANT: Keep sure to return strings as values! Otherwise you might have problems to convert the values to an URL part.
match The complete (unhandled) expression of the argument.
formatExpr The optional format expression of the argument. For {id:number} this will be number.
funcDepth This value is 0 at the beginning. If you return a function in that function again, this will increase until you stop to return a function.

Formatting values

Followed by a : char a route parameter definition can additionally contain a "format expression".

These expressions can help you to parse and format parameter values.

The first step to do this is to define a so called "format provider" callback in a client:

client.addFormatProvider((ctx : ApiClient.IFormatProvider) => {
    var toStringSafe = function() : string { 
        return ctx.value ? ctx.value.toString() : "";
    };

    if (ctx.expression === "upper") {    
        ctx.handled = true;
        return toStringSafe().toUpperCase();  // the new value
    }
    else if (ctx.expression === "number") {
        var n = parseInt(toStringSafe().trim());
        if (isNaN(n)) {
            throw "'" + ctx.value + "' is NOT a number!";
        }
        
        ctx.handled = true;
        return n.toString();
    }
});

Here we defined the two expressions upper (convert to upper case chars) and number (keep sure to have a valid number).

To use them you can define a route like this:

{id:number}/{resource:upper}

Now if you setup your client

var client = ApiClient.newClient({
    baseUrl: "https://api.example.com/users",
    route: "{id:number}/{resource:upper}",  
});

and start a request like this

client.get({
    routeParams: {
        id: "5979",
        resource: "profile"
    }
});

the client will call the URL

[GET]  https://api.example.com/users/5979/PROFILE

The ctx object in the format provider call of addFormatProvider() has the following structure:

interface IFormatProviderContext {
    /**
     * Gets the format expression.
     */
    expression: string;
    
    /**
     * Gets if the expression has been handled or not.
     */
    handled: boolean;
    
    /**
     * Gets the underlying (unhandled) value.
     */
    value: any;
}

Authorization

You can submit an optional IAuthorizer object when you start a request:

interface IAuthorizer {
    /**
     * Prepares a HTTP request for authorization.
     * 
     * @param {HTTP.HttpRequestOptions} reqOpts The request options.
     */
    prepare(reqOpts: HTTP.HttpRequestOptions);
}

The plugin provides the following implementations:

AggregateAuthorizer

var authorizer = new ApiClient.AggregateAuthorizer();
authorizer.addAuthorizers(new ApiClient.BasicAuth("Username", "Password"),
                          new ApiClient.BearerAuth("MySecretToken"));

BasicAuth

var authorizer = new ApiClient.BasicAuth("Username", "Password");

BearerAuth

var authorizer = new ApiClient.BearerAuth("MySecretToken");

OAuth

var authorizer = new ApiClient.OAuth("MySecretToken");
authorizer.setField('oauth_field1', 'field1_value');
authorizer.setField('oauth_field2', 'field2_value');

TwitterOAuth

var authorizer = new ApiClient.TwitterOAuth("<CONSUMER_KEY>", "<CONSUMER_SECRET>",
                                            "<TOKEN>", "<TOKEN_SECRET>");

Requests

GET

// ?TM=5979&MK=23979
client.get({
    params: {
        TM: '5979',
        MK: '23979'
    }
});

POST

client.post({
    content: {
        id: 5979,
        name: "Tanja"
    },
    
    type: ApiClient.HttpRequestType.JSON
});

PUT

client.put({
    content: '<user><id>23979</id><name>Marcel</name></user>',
    
    type: ApiClient.HttpRequestType.XML
});

PATCH

client.patch({
    content: '<user id="241279"><name>Julia</name></user>',
    
    type: ApiClient.HttpRequestType.XML
});

DELETE

client.delete({
    content: {
        id: 221286
    },
    
    type: ApiClient.HttpRequestType.JSON
});

Custom

client.request("FOO", {
    content: {
        TM: 5979,
        MK: 23979
    },
    
    type: ApiClient.HttpRequestType.JSON
});

Logging

If you want to log inside your result / error callbacks, you must define one or more logger actions in a client:

var client = ApiClient.newClient({
    baseUrl: "https://example.com/users",
    route: "{id}",  
});

client.addLogger(function(msg : ApiClient.ILogMessage) {
    console.log("[" + ApiClient.LogSource[msg.source] + "]: " + msg.message);
});

Each action receives an object of the following type:

interface ILogMessage {
    /**
     * Gets the category.
     */
    category: LogCategory;
    
    /**
     * Gets the message value.
     */
    message: any;
    
    /**
     * Gets the priority.
     */
    priority: LogPriority;
    
    /**
     * Gets the source.
     */
    source: LogSource;
    
    /**
     * Gets the tag.
     */
    tag: string;
    
    /**
     * Gets the timestamp.
     */
    time: Date;
}

Now you can starts logging in your callbacks:

client.clientError(function(result : ApiClient.IApiClientResult) {
                       result.warn("Client error: " + result.code);
                   })
      .serverError(function(result : ApiClient.IApiClientResult) {
                       result.err("Server error: " + result.code);
                   })
      .success(function(result : ApiClient.IApiClientResult) {
                    result.info("Success: " + result.code);
               })
      .error(function(err : ApiClient.IApiClientError) {
                 result.crit("API CLIENT ERROR!: " + err.error);
             })
      .completed(function(ctx : ApiClient.IApiClientCompleteContext) {
                     result.dbg("Completed action invoked.");
                 });

The IApiClientResult, IApiClientError and IApiClientCompleteContext objects using the ILogger interface:

interface ILogger {
    /**
     * Logs an alert message.
     * 
     * @param any msg The message value.
     * @param {String} [tag] The optional tag value.
     * @param {LogPriority} [priority] The optional log priority.
     */
    alert(msg : any, tag?: string,
          priority?: LogPriority) : ILogger;
    
    /**
     * Logs a critical message.
     * 
     * @param any msg The message value.
     * @param {String} [tag] The optional tag value.
     * @param {LogPriority} [priority] The optional log priority.
     */
    crit(msg : any, tag?: string,
         priority?: LogPriority) : ILogger;
    
    /**
     * Logs a debug message.
     * 
     * @param any msg The message value.
     * @param {String} [tag] The optional tag value.
     * @param {LogPriority} [priority] The optional log priority.
     */
    dbg(msg : any, tag?: string,
        priority?: LogPriority) : ILogger;
    
    /**
     * Logs an emergency message.
     * 
     * @param any msg The message value.
     * @param {String} [tag] The optional tag value.
     * @param {LogPriority} [priority] The optional log priority.
     */
    emerg(msg : any, tag?: string,
          priority?: LogPriority) : ILogger;
    
    /**
     * Logs an error message.
     * 
     * @param any msg The message value.
     * @param {String} [tag] The optional tag value.
     * @param {LogPriority} [priority] The optional log priority.
     */
    err(msg : any, tag?: string,
        priority?: LogPriority) : ILogger;
    
    /**
     * Logs an info message.
     * 
     * @param any msg The message value.
     * @param {String} [tag] The optional tag value.
     * @param {LogPriority} [priority] The optional log priority.
     */
    info(msg : any, tag?: string,
         priority?: LogPriority) : ILogger;
    
    /**
     * Logs a message.
     * 
     * @param any msg The message value.
     * @param {String} [tag] The optional tag value.
     * @param {LogCategory} [category] The optional log category. Default: LogCategory.Debug
     * @param {LogPriority} [priority] The optional log priority.
     */
    log(msg : any, tag?: string,
        category?: LogCategory, priority?: LogPriority) : ILogger;
    
    /**
     * Logs a notice message.
     * 
     * @param any msg The message value.
     * @param {String} [tag] The optional tag value.
     * @param {LogPriority} [priority] The optional log priority.
     */
    note(msg : any, tag?: string,
         priority?: LogPriority) : ILogger;
     
    /**
     * Logs a trace message.
     * 
     * @param any msg The message value.
     * @param {String} [tag] The optional tag value.
     * @param {LogPriority} [priority] The optional log priority.
     */
    trace(msg : any, tag?: string,
          priority?: LogPriority) : ILogger;
        
    /**
     * Logs a warning message.
     * 
     * @param any msg The message value.
     * @param {String} [tag] The optional tag value.
     * @param {LogPriority} [priority] The optional log priority.
     */
    warn(msg : any, tag?: string,
         priority?: LogPriority) : ILogger;
}

URL parameters

You can befine additional parameters for the URL.

If you create a client like this

var client = ApiClient.newClient({
    baseUrl: "https://api.example.com/users"
});

and start a request like this

client.get({
    params: {
        id: '23979',
        resource: "profile"
    }
});

The client will call the URL

[GET]  https://api.example.com/users?id=23979&resource=profile

Like route parameters you can also use functions for defining URL parameters:

var getUserId = function() : string {
    // load the user ID from somewhere
};

client.get({
    params: {
        id: getUserId,  // {id}
        resource: "profile"  // {resource}
    }
});

A function must have the following structure:

function (paramName: string, index: number, funcDepth: string) : any {
    return <THE-VALUE-TO-USE>;
}
Name Description
paramName The name of the parameter. For {id} this will be id
index The zero based index of the handled URL parameter.
funcDepth This value is 0 at the beginning. If you return a function in that function again, this will increase until you stop to return a function.

IMPORTANT: It is also recommended to use / return strings a parameter values to prevent problems when converting the values to an URL string.

Responses

Callbacks

Simple

client.success(function(result : ApiClient.IApiClientResult) {
                    // handle any response
               });

The result object has the following structure:

interface IApiClientResult extends ILogger {
    /**
     * Gets the underlying API client.
     */
    client: IApiClient;

    /**
     * Gets the HTTP response code.
     */
    code: number;
    
    /**
     * Gets the raw content.
     */
    content: any;
    
    /**
     * Gets the underlying (execution) context.
     */
    context: ApiClientResultContext;
    
    /**
     * Gets the response headers.
     */
    headers: HTTP.Headers;
    
    /**
     * Returns the content as wrapped AJAX result object.
     * 
     * @return {IAjaxResult<TData>} The ajax result object.
     */
    getAjaxResult<TData>() : IAjaxResult<TData>;
    
    /**
     * Returns the content as file.
     * 
     * @param {String} [destFile] The custom path of the destination file.
     * 
     * @return {FileSystem.File} The file.
     */
    getFile(destFile?: string) : FileSystem.File;
    
    /**
     * Tries result the content as image source.
     */
    getImage(): Promise<Image.ImageSource>;
    
    /**
     * Returns the content as JSON object.
     */
    getJSON<T>() : T;
    
    /**
     * Returns the content as string.
     */
    getString() : string;
    
    /**
     * Gets the information about the request.
     */
    request: IHttpRequest;
    
    /**
     * Gets the raw response.
     */
    response: HTTP.HttpResponse;
}

Errors

client.error(function(err : ApiClient.IApiClientError) {
                 // handle an HTTP client error here
             });

The err object has the following structure:

interface IApiClientError extends ILogger {
    /**
     * Gets the underlying client.
     */
    client: IApiClient;
    
    /**
     * Gets the context.
     */
    context: ApiClientErrorContext;
    
    /**
     * Gets the error data.
     */
    error: any;
    
    /**
     * Gets or sets if error has been handled or not.
     */
    handled: boolean;
    
    /**
     * Gets the information about the request.
     */
    request: IHttpRequest;
}

Conditional callbacks

You can define callbacks for any kind of conditions.

A generic way to do this is to use the if() method:

client.if(function(result : IApiClientResult) : boolean {
              // invoke if 'X-My-Custom-Header' is defined
              return undefined !== result.headers["X-My-Custom-Header"];
          },
          function(result : IApiClientResult) {
              // handle the response
          });

If no condition matches, the callback defined by success() method is used.

For specific status codes you can use the ifStatus() method:

client.ifStatus((statusCode) => statusCode === 500,
                function(result : IApiClientResult) {
                    // handle the internal server error
                });

Or shorter:

client.status(500,
              function(result : IApiClientResult) {
                  // handle the internal server error
              });
Short hand callbacks
client.clientError(function(result : ApiClient.IApiClientResult) {
                       // handle status codes between 400 and 499
                   });
                   
client.ok(function(result : ApiClient.IApiClientResult) {
                       // handle status codes with 200, 204 or 205
                   });
                   
client.serverError(function(result : ApiClient.IApiClientResult) {
                       // handle status codes between 500 and 599
                   });

The following methods are also supported:

Name Description
badGateway Handles a request with status code 502.
badRequest Handles a request with status code 400.
clientOrServerError Handles a request with a status code between 400 and 599.
conflict Handles a request with status code 409.
forbidden Handles a request with status code 403.
gatewayTimeout Handles a request with status code 504.
gone Handles a request with status code 410.
informational Handles a request with a status code between 100 and 199.
insufficientStorage Handles a request with status code 507.
internalServerError Handles a request with status code 500.
locked Handles a request with status code 423.
methodNotAllowed Handles a request with status code 405.
notFound Handles a request with status code 404.
notImplemented Handles a request with status code 501.
partialContent Handles a request with status code 206.
payloadTooLarge Handles a request with status code 413.
redirection Handles a request with a status code between 300 and 399.
serviceUnavailable Handles a request with status code 503.
succeededRequest Handles a request with a status code between 200 and 299.
tooManyRequests Handles a request with status code 429.
unauthorized Handles a request with status code 401.
unsupportedMediaType Handles a request with status code 415.
uriTooLong Handles a request with status code 414.