Bixby provides two JavaScript runtime systems your capsule's action implementations can execute on:
Capsules will continue executing on V1 unless your capsule configuration specifically enables V2 through a runtime flag. The new runtime system is not backward compatible. If you wish to transition an existing capsule from V1 to V2, the capsule's code will need to be modified.
The Javascript Runtime selection is "all or nothing": all your capsule's JavaScript will run on the same runtime system. There is no way to specify the engine on a per-function or per-file basis.
The JavaScript runtime version is set by the js-runtime-version
key in your capsule.bxb
file:
capsule {
runtime-version (7) {
js-runtime-version (2)
}
}
If this key is not specified, the runtime version will default to the version set by the current runtime-version
of the capsule. (Currently, this will always be Version 1.)
For more information on the security enhancements made to JavaScript Runtime version 2, see The Bixby Serverless Execution Infrastructure whitepaper.
There are several differences between JavaScript Runtime V1 and V2 to be aware of, both when migrating code manually between runtime versions and when reviewing examples in Bixby documentation.
Bixby's documentation is still being updated with examples for JavaScript Runtime V2, so some examples might still be written in the V1 style. Most sample code for Bixby is also written to V1's API. Translating between the two styles is simple, but you'll need to watch for this difference!
In JavaScript Runtime V1, modules (both ones contained in your capsule and Bixby's JavaScript API modules) are imported using the non-standard require()
function:
var http = require('http')
In V2, you should use the standard module import syntax:
import http from 'http'
import niftyModule from './lib/nifty'
You cannot import
a constant directly, but you can set a constant from an imported module's constant after import:
import niftyModule from './lib/nifty'
const FOOBAR = niftyModule.FOOBAR
You cannot require
or import
modules dynamically based on the contents of a variable. Imported modules must be declared statically.
This will work:
// correct
import http from 'http'
Both of these will cause errors:
// will not work
const http = require(filename);
// will not work
import http from filename;
Suppose your capsule has an action to find a Thing
:
action (FindThing) {
type (Search)
collect {
input (arg1) {
type (Arg1)
}
input (arg2) {
type (Arg2)
}
}
output (Thing)
}
In V1, functions receive one or more parameters that correspond to the input
keys in the action
.
function findThing(arg1, arg2, $vivContext) {
return main.findThing(arg1, arg2, $vivContext)
}
In V2, functions instead receive a single parameter, an object with key/value pairs whose keys correspond to the input
keys in the action. You can use destructuring assignment to assign variables from these keys.
function findThing(input) {
const { arg1, arg2, $vivContext } = input
return main.findThing(arg1, arg2, $vivContext)
}
JavaScript's destructuring assignment from object keys is based on order, not name. The keys will be in the order of input
keys in the collect
block, with the $vivContext
key always being last.
In V1, functions are exported using CommonJS's module.exports
method:
const main = require('./lib/main.js')
module.exports.function = function findThing(arg1, arg2, $vivContext) {
return main.findThing(arg1, arg2, $vivContext)
}
In V2, functions are exported using the standard export
syntax. You can specify the handler function as a default export:
import main from './lib/main.js'
export default function (input) {
const { arg1, arg2, $vivContext } = input
return main.findThing(arg1, arg2, $vivContext)
}
While the standard JS export allows other formats, such as export const
or arrow function notation, only export default
or export function endpoint-name
will work for V2 function exports.
This is the matching endpoint:
endpoints {
action-endpoints {
action-endpoint (FindThing) {
local-endpoint (FindThing.js)
accepted-inputs (arg1, arg2, $vivContext)
}
}
}
Rather than using the default export, you can give a function a name, and specify it in the local-endpoint
key.
import main from './lib/main.js'
export function findThing(input) {
const { arg1, arg2, $vivContext } = input
return main.findThing(arg1, arg2, $vivContext)
}
The findThing
function must be specified:
endpoints {
action-endpoints {
action-endpoint (FindThing) {
local-endpoint (FindThing.js::findThing)
accepted-inputs (arg1, arg2, $vivContext)
}
}
}
A single JavaScript file can export more than one named function, but can only export one default function.
Both runtime versions include a set of internal modules to provide new functions.
Versions 1 and 2 both include the following modules:
base64
config
console
fail
http
md5
secret
textLib
transaction
types
The only difference between runtime versions for these modules is the importing method.
// JS Runtime V1 import
var http = require('http')
// JS Runtime V2 import
import http from 'http'
V2 does not include the following modules:
dates
soap
Most of the functionality from V1's dates
module is designed for formatting dates and times in the user's timezone. In V2, dates can be handled by JavaScript's Date
object, and formatted with the built-in Intl.DateTimeFormat
API. For other date/time functionality, try using other methods in the global Intl
object.
When using the native Date
object, the default timezone is UTC, not the user/device locale. If you need to use a date object with an embedded timezone, you will need to access $vivContext
to find the user/device locale and timezone.
The following are specific topics about replacing the dates
module:
getTimeZoneOffset()
You can use toLocaleString
to format a Date
object in a specific timezone:
// create a new Date object set to 8 AM, September 16, 2022, PDT
const myDate = new Date('Sep 16, 2022, 8:00:00 AM GMT-7')
// returns 'Sep 17, 2022, 12:00:00 AM GMT+9'
formattedDate = myDate.toLocaleString('en-US', {
dateStyle: 'medium',
timeStyle: 'long',
timeZone: 'Asia/Tokyo',
})
You can also use Intl.DateTimeFormat
:
// create a new Date object set to 8 AM, September 16, 2022, PDT
const myDate = new Date('Sep 16, 2022, 8:00:00 AM GMT-7')
// returns 'Sep 17, 2022, 12:00:00 AM GMT+9'
formattedDate = new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium',
timeStyle: 'long',
timeZone: 'Asia/Tokyo',
}).format(myDate)
To use the user's locale and timezone, access $vivContext
:
// create a new Date object set to the current date and time (in UTC)
const myDate = new Date()
// return it formatted for the user's locale and timezone
formattedDate = myDate.toLocaleString($vivContext.locale, {
dateStyle: 'medium',
timeStyle: 'long',
timeZone: $vivContext.timezone,
})
You can compute the timezone offset of a specified date and timezone in seconds by using Intl
to format a date with the desired timezone and computing the difference with the UTC date. This example function is one approach.
function getTimeZoneOffset(utcDate, timeZone) {
const options = {
timeZone,
calendar: 'iso8601',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
}
const dateTimeFormat = new Intl.DateTimeFormat(undefined, options)
const parts = dateTimeFormat.formatToParts(utcDate)
const map = new Map(parts.map((x) => [x.type, x.value]))
const year = map.get('year')
const month = map.get('month') - 1 // Subtract one due to months counting from 0
const day = map.get('day')
const hour = map.get('hour')
const minute = map.get('minute')
const second = map.get('second')
const ms = utcDate.getMilliseconds()
const tzFormattedDate = new Date(year, month, day, hour, minute, second, ms)
// Positive offset is ahead of UTC while negative offset is behind UTC
return -(tzFormattedDate - utcDate) / 60 / 1000
}
// example usage
console.log(getTimeZoneOffset(new Date(), 'Asia/Seoul'))
Calculations with date and time in JavaScript can be accomplished with get
and set
methods on Date
objects:
function addHours(date, hours) {
date.setHours(date.getHours() + hours)
}
function addDays(date, days) {
date.setHours(date.getHours() + days * 24)
}
const event = new Date('August 19, 1975 23:15:30')
addDays(event, 1)
You can use the getTimeZoneOffset
function described above to handle calculations across timezones.
If you use the Simulator's GPS/Clock Override,
your capsule will need to explicitly check the $vivContext.testToday
variable to receive the date/time set in the Simulator, given in milliseconds since January 1, 1970. This variable is undefined
when the capsule is not running in the Simulator or the clock is not being overridden.
// set myDate to the real current time or the override in the Simulator
myDate = new Date($vivContext.testToday ?? Date.now())
If your capsule needs to communicate with a web service via SOAP, you can use template literals to create the XML payload and send it to the remote server using http.postUrl()
. By sending it with the xmljs
format parameter, the returned XML will be translated into JSON.
import http from 'http'
import config from 'config'
export function handler(input) {
const options = {
format: 'xmljs',
returnHeaders: true,
}
const soapMessage = `
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ord="http://...">
<soapenv:Header/>
<soapenv:Body>
<ord:order>
<ord:firstName>${recipient.firstName}</ord:firstName>
<ord:lastName>${recipient.lastName}</ord:lastName>
<ord:address>${recipient.address}</ord:address>
<ord:deliveryDate>${deliveryDateStr}</ord:deliveryDate>
<ord:product>
<ord:sku>${product.sku}</ord:sku>
<ord:quantity>1</ord:quantity>
</ord:product>
<ord:totalAmount>${charges}</ord:totalAmount>
</ord:orders>
</soapenv:Body>
</soapenv:Envelope>`
const response = http.postUrl(config.get('remote.url'), soapMessage, options)
return response
}
async
/await
, which are not supported.JavaScript 1.6's for each...in
loop construction is not supported by V2; instead, use for...in
:
const foo = { a: 10, b: 20 };
// DO NOT USE the unsupported for each...in style
for each (var x in foo) {
console.log(x);
}
// use the supported for...in style
for (let key in foo) {
let x = foo[key];
console.log(x);
}
Existing for each
loops in your capsule will not be automatically migrated between V1 and V2.
Runtime Version 2 uses ESLint for detecting validation errors.
Bixby Developer Studio will not read .eslintignore
or .eslintrc
files. You can, however, use configuration comments for disabling rule warnings in a file. You can disable the following:
// eslint-disable-next-line
// eslint-disable-next-line quotes
/* eslint-disable quotes
This could be useful if your capsule includes an external library that will not otherwise pass validation.
Bixby Developer Studio can assist you in migrating from JavaScript Runtime Version 1 to Version 2. You'll see a message in the Warnings & Errors pane of the editor in your capsule.bxb
file indicating that the V2 runtime is available and suggesting an upgrade with the Quick Fix option.
Click Migrate Capsule Code… to begin the migration process. Migrate to Latest JS Runtime… will also appear as an option in Bixby Studio's context menu when you right-click on the capsule in the file sidebar.
When the migration is complete, Bixby Studio will display a results page showing the following information:
If you have files that cannot be automatically migrated, such as ones using the V1 dates
API, you can use the API Differences section of this document as a guide for making the necessary changes.