A NativeScript module for simply calling HTTP based APIs.
This module is part of nativescript-toolbox.
- Android
- iOS
Run
tns plugin add nativescript-apiclient
inside your app project to install the module.
For quick start have a look at the plugin/index.ts or use the "IntelliSense" of your IDE to learn how it works.
Otherwise...
import ApiClient = require("nativescript-apiclient");
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 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. |
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;
}
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:
var authorizer = new ApiClient.AggregateAuthorizer();
authorizer.addAuthorizers(new ApiClient.BasicAuth("Username", "Password"),
new ApiClient.BearerAuth("MySecretToken"));
var authorizer = new ApiClient.BasicAuth("Username", "Password");
var authorizer = new ApiClient.BearerAuth("MySecretToken");
var authorizer = new ApiClient.OAuth("MySecretToken");
authorizer.setField('oauth_field1', 'field1_value');
authorizer.setField('oauth_field2', 'field2_value');
var authorizer = new ApiClient.TwitterOAuth("<CONSUMER_KEY>", "<CONSUMER_SECRET>",
"<TOKEN>", "<TOKEN_SECRET>");
// ?TM=5979&MK=23979
client.get({
params: {
TM: '5979',
MK: '23979'
}
});
client.post({
content: {
id: 5979,
name: "Tanja"
},
type: ApiClient.HttpRequestType.JSON
});
client.put({
content: '<user><id>23979</id><name>Marcel</name></user>',
type: ApiClient.HttpRequestType.XML
});
client.patch({
content: '<user id="241279"><name>Julia</name></user>',
type: ApiClient.HttpRequestType.XML
});
client.delete({
content: {
id: 221286
},
type: ApiClient.HttpRequestType.JSON
});
client.request("FOO", {
content: {
TM: 5979,
MK: 23979
},
type: ApiClient.HttpRequestType.JSON
});
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;
}
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.
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;
}
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;
}
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
});
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 . |