nativescript-apiclient
Version:
NativeScript module for simply calling HTTP based APIs.
833 lines (654 loc) • 21 kB
Markdown
[](https://www.npmjs.com/package/nativescript-apiclient)
[](https://www.npmjs.com/package/nativescript-apiclient)
# NativeScript API Client
A [NativeScript](https://nativescript.org/) module for simply calling HTTP based APIs.
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G88PA3Q7FFSGN)
## NativeScript Toolbox
This module is part of [nativescript-toolbox](https://github.com/mkloubert/nativescript-toolbox).
## License
[MIT license](https://raw.githubusercontent.com/mkloubert/nativescript-apiclient/master/LICENSE)
## Platforms
* Android
* iOS
## Installation
Run
```bash
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](https://github.com/mkloubert/nativescript-apiclient/blob/master/plugin/index.ts) or use the "IntelliSense" of your IDE to learn how it works.
Otherwise...
## Usage
### Import
```typescript
import ApiClient = require("nativescript-apiclient");
```
### Example
```typescript
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
```typescript
var client = ApiClient.newClient({
baseUrl: "https://api.example.com/users",
route: "{id}/{resource}",
});
```
and start a request like this
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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
```typescript
var client = ApiClient.newClient({
baseUrl: "https://api.example.com/users",
route: "{id:number}/{resource:upper}",
});
```
and start a request like this
```typescript
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:
```typescript
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:
```typescript
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
```typescript
var authorizer = new ApiClient.AggregateAuthorizer();
authorizer.addAuthorizers(new ApiClient.BasicAuth("Username", "Password"),
new ApiClient.BearerAuth("MySecretToken"));
```
### BasicAuth
```typescript
var authorizer = new ApiClient.BasicAuth("Username", "Password");
```
### BearerAuth
```typescript
var authorizer = new ApiClient.BearerAuth("MySecretToken");
```
### OAuth
```typescript
var authorizer = new ApiClient.OAuth("MySecretToken");
authorizer.setField('oauth_field1', 'field1_value');
authorizer.setField('oauth_field2', 'field2_value');
```
### TwitterOAuth
```typescript
var authorizer = new ApiClient.TwitterOAuth("<CONSUMER_KEY>", "<CONSUMER_SECRET>",
"<TOKEN>", "<TOKEN_SECRET>");
```
## Requests
### GET
```typescript
// ?TM=5979&MK=23979
client.get({
params: {
TM: '5979',
MK: '23979'
}
});
```
### POST
```typescript
client.post({
content: {
id: 5979,
name: "Tanja"
},
type: ApiClient.HttpRequestType.JSON
});
```
### PUT
```typescript
client.put({
content: '<user><id>23979</id><name>Marcel</name></user>',
type: ApiClient.HttpRequestType.XML
});
```
### PATCH
```typescript
client.patch({
content: '<user id="241279"><name>Julia</name></user>',
type: ApiClient.HttpRequestType.XML
});
```
### DELETE
```typescript
client.delete({
content: {
id: 221286
},
type: ApiClient.HttpRequestType.JSON
});
```
### Custom
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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
```typescript
var client = ApiClient.newClient({
baseUrl: "https://api.example.com/users"
});
```
and start a request like this
```typescript
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:
```typescript
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:
```typescript
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
```typescript
client.success(function(result : ApiClient.IApiClientResult) {
// handle any response
});
```
The `result` object has the following structure:
```typescript
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
```typescript
client.error(function(err : ApiClient.IApiClientError) {
// handle an HTTP client error here
});
```
The `err` object has the following structure:
```typescript
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:
```javascript
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:
```javascript
client.ifStatus((statusCode) => statusCode === 500,
function(result : IApiClientResult) {
// handle the internal server error
});
```
Or shorter:
```javascript
client.status(500,
function(result : IApiClientResult) {
// handle the internal server error
});
```
##### Short hand callbacks
```typescript
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`. |