Uh oh! We couldn’t find any match.

Please try other search keywords.

Bixby Developer Center

Guides

Implementing JavaScript Actions

Functions are the implementations of actions. They actually execute the steps of a plan, by making computations or contacting external APIs. You first define inputs and outputs within an action first. You then implement functions using JavaScript to provide the necessary logic, operations, and to specify the same inputs and outputs as the action.

JavaScript Functions

The server-side JavaScript environment supports the JavaScript 1.8 (ECMAScript 2017) language and various platform-specific library features.

Concept values are mapped to and from JavaScript objects automatically on input and output. The parameters passed to the function are the input keys in the action, and must be given the same names in the JavaScript function signature that they are given in the models.

Concept model primitives are mapped to and from corresponding JavaScript types. Numeric types are mapped to JavaScript numbers and string types are mapped to strings. Structures are mapped to JavaScript objects, with property names as keys. Values defined with multiple cardinality (i.e., max (Many)) will be passed as a JavaScript array. For optional arguments, you can either specify them with values or leave them undefined with no value in JavaScript.

You could also decide that a default value is not necessary, in which case the value would be null.

Here's an example of a JavaScript function that does actual calculation work of calculating tips. It takes the various inputs (such as serviceBillTotal and salesTaxRate), performs calculations, and returns an array of outputs, which includes the tip.

function calculate(serviceBillTotal, customTipPercent, billSplit, hasTax,
salesTaxRate, isBillPrimary, exclusions)
{
// Messages logged to the console will be available to the Bixby Developer Studio debugger
console.log(customTipPercent)

// Format arguments
customTipPercent = customTipPercent ? parseInt(customTipPercent, 10) / 100 : null;
tipPercent = customTipPercent;

// Handle the optional argument
if (!billSplit) {
billSplit = 1;
}

// Specify local variables
var billTotal = serviceBillTotal.value,
exclusions = exclusions && exclusions.value ? exclusions.value : 0,
baseTotal = billTotal - exclusions,
taxRate = hasTax && salesTaxRate ? salesTaxRate.rate : 0,
taxAmount = 0,
individualTax = 0,
placeName = hasTax && salesTaxRate ? salesTaxRate.placeName : 'Unknown';

// Compute tax
if (!!taxRate) {
taxAmount = baseTotal * (taxRate / 100);
individualTax = taxAmount;

// Reduce bill total by tax amount
baseTotal -= taxAmount;
}

// base total = total - exclusions - tax
var individualBase = baseTotal,
individualExclusions = exclusions;

// Split logic
if (billSplit > 1) {
individualBase = baseTotal / billSplit;
individualTax = taxAmount / billSplit;
individualExclusions = exclusions / billSplit;
}

// Calculate tip and new total
var individualTip = individualBase * tipPercent;

// The output is expecting a money type of { value, currencyType }
var money = function (val) {
return {
currencyType: serviceBillTotal.currencyType,
value: val
};
};

return [{
tip: money(individualTip),
tax: money(taxAmount),
tipPercent: tipPercent * 100,

// Add back in the tax amount after calculating the tip
youPayWithTip: money(individualBase + individualTip + individualTax + individualExclusions),
youPayWithoutTip: money(individualBase + individualTax + individualExclusions),

billSplit: billSplit,
billTotal: money(billTotal),
grandTotal: money(billTotal + (individualTip * billSplit)),

salesTaxRate: taxRate,
placeName: placeName,

// Coerce boolean
hasTax: !!hasTax,
isBillPrimary: !!isBillPrimary,

subject: isBillPrimary ? 'bill' : 'tip',
exclusions: money(exclusions)
}];
}

module.exports = {
function: calculate
}

Passing User Context Information

In some functions, you may need to pass in information about the user, such as user ID, timezone, or locale. In cases like this, you can use the $vivContext parameter to access user-specific information. This is especially useful for capsules that support multiple locales and customize the response based on the user's locale.

Here is the available data from $vivContext:

  • locale: IETF BCP 47 language tag. Example: en-US.
  • timezone: Time zone as per tz database. Example: America/New_York.
  • userId: Unique ID generated based on a given capsule and user.
  • accessToken: OAuth access token (used with Endpoints).
  • sessionId: Unique identifier for a specific conversation by a specific user. You can pass the sessionID through your external system to track that different requests are from the same conversation by the same user.
  • testToday: The time in milliseconds that was set by the Simulator.
  • device: The device class ID. Examples include bixby-mobile.
  • canTypeId: The CAN type ("target") in which the execution is taking place. Example: bixby-mobile-en-US.
  • handsFree: Boolean indicating whether the user is on a hands-free device.
  • storeCountry: The store country specified by the client, denoted in ISO 3166-1 alpha-2. Example: US or UK.
  • deviceModel: The specific device model of the client. Example: SM-G965N.
  • screenLocked: Boolean indicating whether the device's screen is locked.
  • grantedPermissions: Dictionary of permissions granted by the user for the top level capsule.
  • is24HourFormat: Boolean indicating whether the device is set to display time in 24-hour format (true) or AM/PM format (false).
  • networkCountry: The country of the device's current network, denoted in ISO 3166-1 alpha-2. Example: US or UK.

Here is an example of using $vivContext to get the locale.

First, create an action model:

action (AccessVivContext) {
type(Calculation)
collect {
input (dummyInput) {
type (viv.core.Integer)
}
}
output (viv.core.Text)
}

In your corresponding JavaScript file, access the $vivContext.locale parameter to return the locale information.

module.exports.function = function accessVivContext (dummyInput, $vivContext) {
var result = "Testing Access vivContext..."

result = $vivContext.locale
return result
}

You would then need to configure your endpoints for this action like below, including making sure that you set up the proper accepted-inputs for your endpoint:

action-endpoint (AccessVivContext) {
accepted-inputs (dummyInput, $vivContext)
local-endpoint ("AccessVivContext.js")
}

Bixby can then return the locale for you:

Result from calling the AccessVivContext action

Note

As with the other function parameters, Bixby assigns values based on the parameter variable names, so you must use $vivContext as the parameter name to receive the context information.

Capsule Context and State

Context between capsules is not stateful, meaning that if the user leaves the capsule for another capsule or if the user leaves Bixby altogether, then any context for the first capsule is not guaranteed to be remembered.

If your capsule does need to remember context between various states, you should use the content provider. You can store as much information on the content provider side as needed. You can always use remote endpoints and set up your service how you want.

Throwing Exceptions

The server-side JavaScript environment provides a helper library fail with two error types: validation errors and checked errors.

Things can sometimes go wrong inside a function. You should anticipate what might go wrong and use input validation to catch potential errors. But if the validation is being done within JavaScript--for complicated computations to determine input validity, or determining validity based on the results of an external service--you can throw a validation exception.

To report a problem with a specific input or multiple inputs, throw a fail.checkedError as shown below:

throw fail.checkedError(myError, 'UnsupportedCondition', {})

The first parameter, myError, is logged by Bixby through the function implementation, but users won't see it. You can use it for debugging. You should write any user-visible dialog as part of the model. Use the object in the third parameter ({}) to pass any expected properties to the action model's error handler declaration:

...
output (Activity) {
throws {
error (UnsupportedCondition) {
on-catch {
halt {
dialog {
template ("Actual dialog of error message")
}
}
}
}
}
}
...

Here is a more robust example that accounts for multiple errors:

    if (response.parsed.error) {
switch (response.parsed.error.code) {
case "VALIDATION_ERROR":
if (response.parsed.error.description.indexOf("Number too big:") === 0 ||
response.parsed.error.description.indexOf("is greater than the maximum of") >= 0) {
throw fail.checkedError(JSON.stringify(params), "ReservationPartySizeTooBig");
}
if (response.parsed.error.description.indexOf("Number too small:") === 0) {
throw fail.checkedError(JSON.stringify(params), "ReservationPartySizeTooSmall");
}
if (response.parsed.error.description === "You're trying to book a reservation in the past, please pick a time in the future." ) {
throw fail.checkedError(JSON.stringify(params), "ReservationPast");
}
case "REQUEST_TIMED_OUT":
throw fail.checkedError(JSON.stringify(params), "RequestTimedOut")
default:
throw Error("Capsule error: " + JSON.stringify(response.parsed.error))
}
throw Error("Capsule error: " + JSON.stringify(response));
}

When testing your function, you can expect to see details of any errors thrown by functions.

For more information and examples, read about Error Handling.

Calling Web Services

Within functions, you can add API calls for both SOAP and REST-based web services.

REST

To call a REST-based web service, use one of these calls:

http.getUrl(url, options)

http.postUrl(url, paramsOrBody, options)

http.putUrl(url, paramsOrBody, options)

http.deleteUrl(url, paramsOrBody, options)

In each case, url is the URL of the service.

For a POST call, paramsOrBody are the parameters to pass as the body of the request. Normally, it should be an object with the desired keys and values, and these will be HTTP form encoded. However, if a different format for the body is required, it is possible to use passAsJson (for JSON-formatted bodies), or a fully custom format (such as XML) by passing a string instead of an object. In the latter case, you might want to set the Content-Type in the headers option.

The options argument is a JSON object that can contain the following parameters:

  • format: This specifies how the response will be processed. It may be one of 'text', 'json', 'xmljs', or 'csv'.
  • query: An object with attribute/value pairs to pass as arguments to the REST service.
  • basicAuth: HTTP Basic Authentication. The value must be an object with "username" and "password".
  • cacheTime: Cache time in milliseconds. By default, all GET requests are cached in memory. An alternate cache time may be provided.
  • headers: An object with key/value pairs for additional HTTP headers. This may be used for custom Content-Type settings (such as XML).
  • passAsJson (POST call only): If set to true, passes the request parameters in the body in JSON format and sets the content type appropriately.
  • returnHeaders: If set to true, returns HTTP headers and status code. This also suppresses exceptions on HTTP error status codes, leaving this to the developer to handle.

Here's an example:

var response = http.getUrl('https://api.example.com', {
format: 'json',
query: {
booleanAttribute: booleanAttribute,
name: name,
restaurantStyle: restaurantStyle,
address: address,
menuItem: menuItem
}
})

The library makes an HTTP request (with a timeout, to prevent excessive server resource usage), and processes the response according to the format specifier. Each format specifier works to map between the response and a JavaScript data structure. The 'text' format simply returns a string and is useful for text, or any response where you wish to do the parsing manually in JavaScript. The JSON format maps to JavaScript objects in a straightforward way.

The xmljs format maps an XML response to JSON using StAXON conventions. The result can then be accessed as a JavaScript object. For example, consider this XML response:

<reviews>
<review id="166739">
<content>Great!...</content>
<reviewer>Teschia</reviewer>
<star-rating>4</star-rating>
<order-count>92</order-count>
<review-count>31</review-count>
</review>
<review>
...
</review>
<reviews>

The above would map to this JSON structure:

{
"reviews" : {
"review" : [
{
"@id" : "166739",
"content" : "Great!...",
"reviewer" : "Teschia",
"star-rating" : "4",
"order-count" : "92",
"review-count" : "31"
},
...
]
}
}

So it is then possible to access fields via expressions like response.reviews.review[0].content.

For XML and JSON responses, you should use a construct similar to the following to verify that a document was parsed and has the expected structure:

var content = reviews.review && reviews.review[0] && reviews.review[0].content

In addition, the XML-to-JSON mapping can return single values or arrays, depending on whether there is one element or a sequence of elements of the same name. If you know a variable should be an array, even if there is only one element, you can write something like:

var reviewsList = [].concat(response.review)

The CSV format maps a CSV-formatted result to an array of arrays.

Status Codes

By default, if the status code is any HTTP error code (less than 200 or 4xx or 5xx) an exception is thrown. However, if you enable the returnHeaders option, this behavior is disabled, and you will get an object containing details of the returned content, including these fields:

  • status is the HTTP status code
  • headers contains the HTTP headers
  • responseText contains the raw HTTP response body, as a string
  • parsed contains the parsed response (depending on the format option)

SOAP

SOAP web service calls can be made via this call:

soap.call(uri, soapAction, methodName, parameters, namespaces)

The related parameters are:

  • uri is the endpoint of the service.
  • soapAction is the SOAP action (specified in the WSDL, and in some cases may be the empty string).
  • methodName is the method to call.
  • parameters are the method parameters, in JavaScript form. Basic string types and structures are supported.
  • namespaces is a JavaScript object with all XML-style namespace abbreviations used in the parameters or method name. (See below for an example.)

The response simply maps the XML of the SOAP response to JavaScript using the same StAXON conventions as with REST requests.

Here is a sample call:

soap.call(
"http://ws.cdyne.com/emailverify/Emailvernotestemail.asmx",
"http://ws.cdyne.com/VerifyEmail",
"ns:VerifyEmail",
{ "ns:email": "hmmm@gmail.com", "ns:LicenseKey": 123 },
{ "ns": "http://ws.cdyne.com/" } // Namespaces
)

This is the resulting response:

{ @xmlns:soap: "http://schemas.xmlsoap.org/soap/envelope/",
VerifyEmailResponse:
{ @xmlns:xmlns: "http://ws.cdyne.com/",
VerifyEmailResult:
{ ResponseText: "User Not Found",
ResponseCode: "4",
LastMailServer: "gmail-smtp-in.l.google.com",
GoodEmail: "false"
}
}
}

For convenience, the following namespaces are always predefined, so you don't need them in the namespaces argument:

  • soap for http://schemas.xmlsoap.org/soap/envelope/
  • xsd for http://www.w3.org/2001/XMLSchema
  • xsi for http://www.w3.org/2001/XMLSchema-instance

OAuth

You can use OAuth 2.0 so that users can authorize access to a third-party service. In general, when OAuth is used, the key players are:

  • The user: The user owns some resource (for example, their list of contacts) that could be made available to functions.

  • The provider: The operator of some service to access this protected resource (for example, Google's contacts service).

  • You, the developer: The developer who implements the function to access the service, which might not be the provider.

Different providers may be registered with Bixby. A user authorizes a developer to access a particular scope of capabilities with the provider. To access OAuth APIs, you must have developer keys registered with the provider, and store them with Bixby. Once the user authorizes access, the developer receives an access key that allows access to user content from the provider.

In general, Bixby manages all these keys. The function script must simply reference the correct URL and required parameters for user authorization.

Here is an example function that uses OAuth to access a user's Google contacts:

exports.function = function (searchTerm) {
var response = http.oauthGetUrl(
// the URL
"https://www.google.com/m8/feeds/contacts/default/full",
// options
{
query: { q: searchTerm },
// Special header required by provider:
headers: { "GData-Version": "3.0" },
format: 'xmljs',
}
)
var contactInfos = ... // Extract data here

return contactInfos
}

exports.authorization = {
'provider': 'google',
'scope': 'https://www.google.com/m8/feeds' }

Note that the function exports an authorization section that specifies the kind of authorization needed. You must include a 'provider' declaration. The provider registration specifies all details needed, such as the OAuth version required by that provider, and other details. You can only use OAuth 2.0. The 'scope' field is optional, but can be supplied to specify access to a particular set of services with the provider.

If a function has a valid authorization section, it can use the OAuth methods of the http package, as shown above. These methods operate in the same manner as regular http package tools, such as http.getUrl() and http.postUrl(), but automatically sign requests with the appropriate access keys. All format handling and other usage is the same as with regular non-OAuth services. Here is a complete list of the OAuth methods in the http package:

When a user makes use of any of these functions the first time, they must authorize the function to access their data at the given provider. If they confirm, Bixby stores the access keys for later invocations.

As part of the OAuth flow, you will be required to register a redirect URI (redirect_uri) with the provider. Bixby's redirect URI must be set to the following:

https://oauth.aibixby.com/auth/external/cb

OAuth can also be used with endpoints. Read about OAuth Authorization with Endpoints.

Note

Bixby does not currently support the OAuth 1.0 protocol. Your capsule must authenticate with OAuth 2.0 providers.

Managing Developer Keys

Many external APIs require developer keys. While some settings for accessing external services might be stored directly in JavaScript configurations, these access keys must be protected to prevent inadvertent disclosure.

You can list your API keys, URLs, and other related details (including OAuth credentials) in the capsule.properties file, which you should place in the resources folder organized by device and locale (Example: capsule/resources/en-US/capsule.properties). Alternatively, you can have one capsule.properties file that applies to all devices and locales in capsule/resources/base.

Example of capsule.properties file:

## Set the config mode of this capsule
# capsule.config.mode=default

## Set configuration properties with `config.<mode>.<propertyKey>=<propertyValue>
## Use mode `default` for fallback property values
# config.default.my.property.key=myValue 123

## To prevent any content in this capsule from loading, set `capsule.enabled` to `false`
# capsule.enabled=false

config.default.oauth.id=abousfklj3_42sfadljf_23223
config.default.oauth.secret=DidDLSDDLKjSDFlkjsfdfsoijoLKjsfOJIOsflm

Once you list your developer keys and URLs, you can reference them within your JavaScript code in the follow manner:

// Exports
module.exports = {
function: getReviews,
preconditions: preconditions,
authorization: {
provider: "ReviewAPI",
type: "OAuth2.0",
grantType: "client_credentials",
clientId: config.get("oauth.id"),
clientSecret: config.get("oauth.secret"),
tokenEndpoint: "https://api.example.com/oauth2/token",
},
}
Note

Unlike user data discussed above, developer JavaScript code has full access to the keys within JavaScript. It is your responsibility to avoid inappropriate use of keys.

Developer Libraries

In addition to the web services modules provided above, Bixby provides a few other libraries for developers to use within their functions. See the JavaScript API documentation for details.

Provider-Specific Identifiers

Many concepts have an inherent ID associated with them, such as a business ID or a product ID, These IDs are provider-specific and are only meaningful to the provider who creates and uses them. For instance, a restaurant ordering ID has no functional meaning to other providers other than the restaurant ordering service. For privacy and accounting reasons, IDs are given special handling by Bixby. No modeling at the concept level is required for IDs -- if a function provider has an ID it wants to associate with an instance it is generating, it can assign a value to the $id property at any level of the structured return object. Then later, any instances passed into a function can be tested for the presence of this $id property. If the ID is associated with the particular function provider, it will be present; otherwise, Bixby filters it out. Often, as part of a function's preconditions, it will test the presence of an $id to ensure that the incoming object was created by itself.

// During the mapping phase in within
// restaurant.FindRestaurants.js, we assign a business id to each returned
// restaurant item
var item = {
$id: result.id,
name: result.name, // optional restaurant.RestaurantName
restaurantStyle: categories, // optional restaurant.RestaurantStyle array
photo: (result.image ? [{ url: result.image, size: "medium"}] : undefined),
businessUrl: result.website, // optional entity.EntityUrl
address: address, // optional geo.Address
review: shared.getReviews(result.id) // optional rating.Review array
}

Function Preconditions

If you have multiple providers implementing functions for an action, you may want to provide a precondition that indicates whether your capsule should call a specific function. For example, an action might have an optional input, but the API you are calling is not structured to handle that optional input, and you don't want to force a default. In cases like this, you do not want that provider-specific implementation to be called.

For example, a function may want to check for a Provider-specific ID, to make sure it recognizes the input structure. You can do this using one or more function preconditions. Simply export an array of preconditions, each of which returns boolean true when the function should run and false otherwise. If any precondition returns false, Bixby will ignore the function and proceed as best it can with another function. If there are no acceptable functions, execution fails.

Here's an example of a precondition from a function to finalize an order:

function hasVendorId(order) {
return !!(order && order.catalog && order.catalog.vendor && order.catalog.vendor.$id);
}
module.exports = {
function: finalizeDeliveryOrder,
preconditions: [ hasVendorId ],
// ... tests ...
};

You don't have to create any preconditions. If the array is empty, then Bixby assumes a true result. The array of preconditions can be as large as you like. Any false result will invalidate the function.

Each precondition function takes one or more parameters, which must have the same names as the function's input. That is, if your function takes (a, b, c), then you can write preconditions using any combination of those, such as (a) or (a, c). In the example above, the function must have an order parameter.

Try to write preconditions with as few parameters as you need to implement your logic. When preconditions fail, Bixby knows the parameters that are used and can expose this information.