Git Product home page Git Product logo

smartapp-sdk-nodejs's Introduction

SmartThings SmartApp Node.js SDK

Language grade: JavaScript Total alerts Known Vulnerabilities

Reference Documentation

For a detailed look at the API, check out the reference documentation.

Getting Started

Version 2.0 Release

This major release is not fully backwards compatible with version 1.X, though for most SmartApps the changes required should be relatively minor. The major non-backwards compatible changes include:

  • Methods that return lists now return arrays instead of objects with the properties items and _links.
  • Axios is now used rather than request-promise-native for making HTTP calls, resulting in changes to the error objects thrown when exceptions occur.

See the Version 2.0.0 release notes for more information.

This SDK includes a set of Node.js libraries for building Webhook and AWS Lambda SmartApps, and interacting with the public SmartThings API.

Highlights include:

  • ✅ Javascript API hides details of REST calls and authentication.
  • ✅ Event handler framework dispatches lifecycle events to named event handlers.
  • ✅ Configuration page API simplifies page definition.
  • ✅ Integrated i18n framework provides configuration page localization.
  • Winston framework manges log messages.
  • ✅ Context Store plugins – easily scale access token management (and more) to support many users.

What is a SmartApp?

SmartApps are custom applications that execute outside of the SmartThings Platform. All of the SmartApp execution will happen on the server or Lambda that you control. The complexity of execution and the number of expected users will need to be examined to understand the hardware or execution requirements your app needs to handle. Your application will respond to lifecycle events sent from SmartThings to perform actions on behalf of your users and will execute any scheduled tasks that you have created in the SmartApp. Creating a SmartApp allows you to control and get status notifications from SmartThings devices using the SmartThings API.

Hosting Your SmartApp

There are two distinct options for hosting your SmartApp: AWS Lambda and Webhook.

  • AWS Lambda SmartApps are hosted in the Amazon Web Services cloud and are invoked by ARN instead of a public-DNS address.

  • Webhook SmartApps are any publicly-accessible web server that will receive a POST request payload.

To learn more about SmartApps, including choosing the best hosting solution for your SmartApp, visit the SmartApp developer documentation.

Installation

npm i @smartthings/smartapp --save

Importing

Node.js:

const SmartApp = require('@smartthings/smartapp')

Or ES2015+:

import SmartApp from '@smartthings/smartapp'

Examples

The example SmartApp below is the equivalent of a simple Rule (if contact sensor opens/closes, turn lights on/off) which is easily achieved via our Rules API. It is given here as a brief showcase of the SDK, and is not meant to be a good candidate for a SmartApp.

Before hosting your own Automation, be sure to check out Rules. When all services and Device features involved in a Rule are local, Rules execute locally on a Hub, allowing you to benefit from greater speed, stability, and security than cloud-reliant solutions.

Running as a Web Service

To run your SmartApp with an HTTP server, such as Express.js:

const SmartApp = require('@smartthings/smartapp');
const express = require('express');
const server = express();
const PORT = 8080;


/* Define the SmartApp */
const smartapp = new SmartApp()
    .enableEventLogging(2) // logs all lifecycle event requests and responses as pretty-printed JSON. Omit in production
    .page('mainPage', (context, page, configData) => {
        page.section('sensors', section => {
            section
                .deviceSetting('contactSensor')
                .capabilities(['contactSensor'])
        });
        page.section('lights', section => {
            section
                .deviceSetting('lights')
                .capabilities(['switch'])
                .permissions('rx')
                .multiple(true);
        });
    })
    // Called for both INSTALLED and UPDATED lifecycle events if there is no separate installed() handler
    .updated(async (context, updateData) => {
        await context.api.subscriptions.delete() // clear any existing configuration
        await context.api.subscriptions.subscribeToDevices(context.config.contactSensor, 'contactSensor', 'contact', 'myDeviceEventHandler');
    })
    .subscribedEventHandler('myDeviceEventHandler', async (context, event) => {
        const value = event.value === 'open' ? 'on' : 'off';
        await context.api.devices.sendCommands(context.config.lights, 'switch', value);
    });

server.use(express.json());

/* Handle POST requests */
server.post('/', function (req, res, next) {
    smartapp.handleHttpCallback(req, res);
});

/* Start listening at your defined PORT */
server.listen(PORT, () => console.log(`Server is up and running on port ${PORT}`));

Running as an AWS Lambda Function

To run your SmartApp as a Lambda function instead of an HTTP server, ensure that your main entry file exports smartapp.handleLambdaCallback(...).

Note that this snippet is heavily truncated for brevity:

const SmartApp = require('@smartthings/smartapp');

const smartapp = new SmartApp()
    .enableEventLogging() // logs all lifecycle event requests and responses. Omit in production
    .page( ... )
    .updated(() => { ... })
    .subscribedEventHandler( ... );

exports.handler = (event, context, callback) => {
    smartapp.handleLambdaCallback(event, context, callback);
};

Additional Examples

You can find additional examples in Glitch:

More detailed examples to use as a starting point can be found in our smartthings-smartapp-example Github Topic.

Localization

Configuration page strings are specified in a separate locales/en.json file, which can be automatically created the first time you run the app. Here's a completed English localization file for the previous simple SmartApp example:

{
  "pages.mainPage.name": "Let There Be Light",
  "pages.mainPage.sections.sensors.name": "When this door or window opens or closes",
  "pages.mainPage.settings.contactSensor.name": "Select open/close sensor",
  "pages.mainPage.sections.lights.name": "Turn on and off these lights and switches",
  "pages.mainPage.settings.lights.name": "Select lights and switches",
  "Tap to set": "Tap to set"
}

Unhandled Promise Rejection Handling

By default, instantiation of the SmartApp object registers an unhandledReject handler that logs unhandled promise rejections. You can disable this behavior by passing an option to the SmartApp instantiation (e.g. new SmartApp({logUnhandledRejections: false})).

If desired, you can replace the handler by calling unhandledRejectionHandler(promise => {...}) on the SmartApp object.

Making API Calls Outside of an EVENT Handler

By default, the SmartApp SDK will facilitate API calls on behalf of a user within the EVENT lifecycle. These user tokens are ephemeral and last 5 minutes. These access tokens are not able to be refreshed and should not be stored.

If you are making out-of-band API calls on behalf of a user's installed app, you will need to use the 24-hour access token that is supplied after the INSTALL and UPDATE lifecycles. This token includes a refresh_token, and will automatically be refreshed by the SDK when necessary.

Note that there is no in-memory context store; you must use a context store plugin. If you'd like to add a custom context store plugin, please consider contributing.

To get started with our context store example below, we will add a compatible ContextStore plugin that will persist these tokens (among other things) to a database.

Amazon AWS DynamoDB

Available as a node package on NPM or fork on GitHub.

If you are hosting your SmartApp as an AWS Lambda, this DynamoDB context store makes perfect sense. This assumes you've already configured the aws-sdk package to interact with your Lambda, so extending your context store to DynamoDB is a drop-in solution.

If you are self-hosted and still want to use DynamoDB, you can do that, too:

  1. Import the package to your project: npm i --save @smartthings/dynamodb-context-store
    • Note: when adding this package, you also have aws-sdk available at the global scope, so you can configure the AWS SDK: AWS.config.loadFromPath(creds)
  2. Get an AWS Access Key and Secret
  3. Set your credentials for your app, any of the following ways are fine.
  4. Register your Context Store plugin as described on the project repository's readme.

For complete directions on usage, please see this project's GitHub repository. (SmartThingsCommunity/dynamodb-context-store-nodejs)

Firebase Cloud Firestore

Available as a node package on NPM or fork on GitHub.

Usage is generally the same as DynamoDB:

  1. Generate a Firebase service account. You will receive a JSON file with the credentials.
  2. Load your Google Services JSON
  3. Create the context store

See the full usage guide on the project's GitHub repository.


More about SmartThings

Check out our complete developer documentation here.

To create and manage your services and devices on SmartThings, create an account in the developer workspace.

The SmartThings Community is a good place share and ask questions.

There is also a SmartThings reddit community where you can read and share information.

License and Copyright

Licensed under the Apache License, Version 2.0

Copyright 2023 Samsung Electronics Co., LTD.

smartapp-sdk-nodejs's People

Contributors

bflorian avatar dependabot[bot] avatar doczillar avatar erodewald avatar jeff-blaisdell avatar john-u avatar lukhong avatar mj17 avatar rossiam avatar saulfloresbluetrail avatar schawla3 avatar sitlintac avatar smartthingspi avatar snyk-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

smartapp-sdk-nodejs's Issues

Setting multiple permissions is a bit confusing

Is your feature request related to a problem? Please describe.
When setting a singular permissions, it's obvious and just works, but when you have to set multiple, it's easy to get tripped up.

VALID

smartapp.permissions('r:devices:*')`

INVALID

let perms = 'r:devices:* x:devices:*'
smartapp.permissions(perms)

VALID

let perms = 'r:devices:* x:devices:*'
smartapp.permissions(perms.split(' '))

Describe the solution you'd like
Analyze the incoming permissions string, object, or array and transform it intelligently.

Describe alternatives you've considered
Documentation – make it clear that we can support both a single permissions via string, or a strain array.

Additional context
When you do this wrong, it is very difficult to see why configuration doesn't work. Strongman will error out and you can figure it out with DevWorkspace's Live Logging but it honestly didn't even occur to me to look there for far too long. 🙄

Request to support localized string to options & defaultValue of enum-setting

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-> Options in enum-setting are often required localization.

Describe the solution you'd like
A clear and concise description of what you want to happen.
-> It would be great if it works like below.
<app.js>
section .enumSetting(enumSettingId);
<en.json>
{ "pages.mainPage.settings.enumSettingId.name": "Enum Setting No.1", "pages.mainPage.settings.enumSettingId.options": ["Option A", "Option B"] }

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
-> If smartapp-sdk-nodejs doesn't support it, then developers should handle this in their own app.

Additional context
Add any other context or screenshots about the feature request here.

Custom Response handling

Is your feature request related to a problem? Please describe.
Add the ability to control the response sent back to the SmartThings API.

Describe the solution you'd like
Let me send back a response of my choice when handling lifecycle events, such as replying with a 404 when a unknown configuration page is requested.

Describe alternatives you've considered
Let me pick the code used to reply to the request.

Additional context
Request return codes are visible in the logs on SmartThings, and using appropriate codes helps debug.

Mode subscriptions need some work

Is your feature request related to a problem? Please describe.
Subscribing to mode changes is possible, that's great, except proper usage is unclear. There's no subscriptionName for modes.

In smart-app.js#L177-178, it is clear that we can subscribe by name.

It is evident in smart-app.js#L411-L415 that the name is simply overridden.

See example below: (this works, but it's confusing)

 .updated((context, updateData) => {
    context.api.subscriptions.unsubscribeAll().then(() => {
      context.api.subscriptions.subscribeToModeChange()
    })
  })
  // "modeChangeHandler" is basically discarded, so how is this handler functioning...?
  .subscribedEventHandler('modeChangeHandlerDoesntMatterWhatThisIsCalled', (context, event) => {
    const shouldTrigger = context.config.modes.some(cfg => {
      return cfg.modeConfig.modeId === event.modeId
    })
    console.log('ShouldTrigger:', shouldTrigger)
})

Describe the solution you'd like
Maybe there should be a unique function instead of re-using subscribedEventHandler().

Describe alternatives you've considered
Ask responsible service to allow naming subscriptions to modes (and others) for our own sanity.

Additional context
n/a

EXECUTE Lifecycle handler does not send response back to ST Cloud

Describe the bug
To support private and future lifecycle events, we need to allow for a fall-through handler. This handler must pass what is needed to the implementation to respond to the event.

To Reproduce
Steps to reproduce the behavior:
Use service plugin at mobile client to request.

Expected behavior
Service plugin should receive response.

Actual behavior
Service plugin does not receive any response from ST Cloud.

Additional context
case 'EXECUTE': {
this._log.event(evt)
await this._executeHandler(context, evt.executeData)
break
}
handler in smart-app.js should send response use responder or pass responder object to custom executeHandler to respond at custom handler.

Logging broken

Describe the bug
Logging is broken, depending on how you configure it.

To Reproduce
Steps to reproduce the behavior:

  1. smartapp.configureLogger(console, 2, true)
  2. Receive a lifecycle event
  3. Nothing is logged

Expected behavior
Output is found in console.

Actual behavior
Log(logger, jsonSpace, enableEvents) constructor won't set this._logger = logger unless a logger object is not passed in.

Screenshots
N/A

Add CI build / codecov badges

Let's make sure that the CircleCI build badge is on the readme, along with the Codecov.io badge much like the Java project.

Support new app confirmation flow

Is your feature request related to a problem? Please describe.
The means of verifying requests via public keys exhibits the "chicken before the egg" issue, so the confirm flow was created to alleviate those woes. The SDK should support this flow.

Through the api.smartthings.com/v1/apps API's query parameters, a developer can create an app with different signature types.

Param Type Values Description
signatureType string APP_RSA ST_PADLOCK The Signature Type of the application. For WEBHOOK_SMART_APP only.
requireConfirmation boolean true false Override default configuration to use either PING or CONFIRMATION lifecycle. For WEBHOOK_SMART_APP only.

Describe the solution you'd like
The confirm flow requires some manual processing by a developer, e.g., making a GET request to a specified URL. The URL should be printed to the console so the developer can confirm their app.

Describe alternatives you've considered
N/A

Additional context
Existing public keys can be retrieved from an HTTP source – the SDK must provide a "migration path" should already have an existing public key.

Improve code coverage

It's sitting at about 30%, ideally we'd be around 60-70%. More for the sake of more is not necessary, but we have a lot of variability that we are not catching right now.

Prime candidates for unit tests would be Page settings classes – start w/ the base classes.

Capability integration

Is your feature request related to a problem? Please describe.
Developers should not need to look up capabilities in documentation and then type them and their attributes in as strings.

Describe the solution you'd like
Capability constants generated by capabilities reference

// before
subscribe('myHandlerName', 'capability', 'attribute', args)

// proposed (example 1), subscribing to capability "Button", attribute "button"
import { capabilities } from '@smartthings/smartapp'
subscribe('myHandlerName', capabilities.button, capabilities.button.button, args)

Describe alternatives you've considered
Expanding on this... the Subscriptions API (and adjacent APIs) could accept a single argument instead.

// before
subscribe('myHandlerName', 'button', 'button', args)

// proposed (example 1), subscribing to capability "Button", attribute "button"
import { capabilities } from '@smartthings/smartapp'
subscribe('myHandlerName', capabilities.button.button, args)

// proposed (example 2), subscribing to capability "Button", attribute "numberOfButtons"
import { capabilities } from '@smartthings/smartapp'
subscribe('myHandlerName', capabilities.button.numberOfButtons, args)

Additional context
n/a

List Scenes/Subscriptions API calls may be broken

Describe the bug
The following line(s) appears to be broken due to:

  • A) it references st.api instead of st.client
  • B) listRequest() is not a function

Relevant references:

To Reproduce
Steps to reproduce the behavior:

  1. Make API calls subscriptions.list() or scenes.list()
  2. see console error

Expected behavior
Should return the expected response

Actual behavior
Throws error

Additional context
Note that client.listRequest() was likely designed at one point to iterate over multiple pages using the HATEOAS object. Now, I am seeing that most list requests simply set the request's page size to 500 as a quick means to get around not having a listRequest function. @bflorian am I close to understanding this?

As a separate issue (to be created) – this must be changed because devices across locations (and potentially server regions) would come back in different pages even if they were below the per-page maximum.

Generate API documentation

Is your feature request related to a problem? Please describe.
Currently, one needs to read through code to find this documentation.

Describe the solution you'd like
There is API documentation published somewhere and linked to from the README.

Describe alternatives you've considered
none

Additional context
none

Token management

Is your feature request related to a problem? Please describe.
I want to make an out-of-band API request (not originating from an EVENT). I can do that with app.withContext(...) but what if the token's access is expired? What about current "context" management with auth tokens? In-memory is not viable for production.

Describe the solution you'd like

  • A pluggable solution to persist contexts to ______ library of choice.
  • An automatic refreshing mechanism to ensure out-of-band API requests do not 401 due to expired authToken from stored contexts.

Describe alternatives you've considered
n/a

Additional context
Learn more about the endpoint context system here:
https://github.com/SmartThingsCommunity/smartapp-sdk-nodejs/blob/master/lib/util/endpoint-context.js

Pardon my ignorance – perhaps this is partially solved by this code: https://github.com/SmartThingsCommunity/smartapp-sdk-nodejs/blob/master/lib/platform/client.js#L78-L101

keys duplicating in I18n `/locales/en.json` file after app restart

Describe the bug
This is regarding the/locales/en.json file which is used for localization of the configuration lifecycle. Upon restarting nodejs app there are new (duplicate) keys being appended in the file.

Example:

The last entry here pages.mainPage.sections.lights.name was duplicated after restarting the nodejs app.

{
	"pages.mainPage.name": "Nodejs Sample SmartApp - Let there be Light",
	"pages.mainPage.settings.lights.name": "Select lights  and  switches",
	"Tap to set": "Select Devices",
	"pages.mainPage.sections.lights.name": "pages.mainPage.sections.lights.name"
}

To Reproduce

  1. Build app using the codesnip available in readme (for endpointapp) or include the following codesnip ...
smartapp.publicKey(`@./smartthings_rsa.pub`)
    .configureI18n()
    .page('mainPage', (response, page) => {
        page.section('lights', (section) => {
            section.deviceSetting('lights').capabilities(['switch']).multiple(true).permissions('rx');
        });
    })
  1. Run through SmartApp Installation using SmartThings mobile app (this will generate the file 1st time round.

  2. Observe file is generated locales/en.json. Then restart the nodejs app and repeat above steps.

  3. Observe file again locales/en.jsonand note the keys duplicated per description.

Expected behavior
File should not be modified:

{
	"pages.mainPage.name": "Nodejs Sample SmartApp - Let there be Light",
	"pages.mainPage.settings.lights.name": "Select lights  and  switches",
	"Tap to set": "Select Devices",
}

Actual behavior
Duplicate key is added

{
	"pages.mainPage.name": "Nodejs Sample SmartApp - Let there be Light",
	"pages.mainPage.settings.lights.name": "Select lights  and  switches",
	"Tap to set": "Select Devices",
	"pages.mainPage.sections.lights.name": "pages.mainPage.sections.lights.name"
}

Screenshots
N/A

Environment (please complete the following information):
N/A

Additional context
N/A

Cannot cascade (method chain) after call to `.subscribedEventHandler(...)`

Describe the bug

Cannot cascade (method chain) after call to .subscribedEventHandler(...). Causes unexpected token on next cascaded method call.

Example:

.installed((context, ...)=>{}
.subscribedEventHandler((context, ...)=> {}
.deviceCommandHandler((context, ...) => {}

Error:

.deviceCommandHandler((context, ...) => {
  ^
SyntaxError: Unexpected token .

Observed that switching the order '.deviceCommandHandler' and '.subscribedEventHandler' works.

Will be helpful to note and evaluate which functions can (and cannot) be cascaded.

To Reproduce
See example snip above.

Expected behavior
If this is allowed:

.installed((context, ...)=>{}
.deviceCommandHandler((context, ...) => {}
.subscribedEventHandler((context, ...)=> {}

Can we expect this also be allowed (order switched)

.installed((context, ...)=>{}
.subscribedEventHandler((context, ...)=> {}
.deviceCommandHandler((context, ...) => {}

Additional context
Add any other context about the problem here.

Add support for context store `state` object

Is your feature request related to a problem? Please describe.
The 2.0 dynamodb nodejs lib includes a new feature (addition of arbitrary state data object, and omission of clientId, clientSecret). The SDK needs to support this.

Describe the solution you'd like
endpoint-context.js needs some functions to get/put this data whenever they want.

e.g.,
getState() : Promise<object>
putState(state) : void

Describe alternatives you've considered
n/a

Additional context
This needs to include unit tests, as we're already fighting abysmal code coverage numbers.

Add Rooms API

Is your feature request related to a problem? Please describe.
Rooms API is missing

Additional context
A Room can be thought of as a subset of a Location, a way to further organize devices within the Location.

Required authorization scope: r:locations:*

List: GET /locations/:locationId/rooms
Create: POST /locations/:locationId/rooms
Get: GET /locations/:locationId/rooms/:roomId
Update: PUT /locations/:locationId/rooms/:roomId
Delete: DELETE /locations/:locationId/rooms/:roomId

question: Notifications API documentation

I'm happy to see, by looking at the source code, that there is a notification REST API available for SmartThings, that can be used to push notifications. Although I can't find any documentation from Samsung about this API.

When using the notifications API, I receive notifications on my mobile devices via the SmartThings app, but not on my TV (whereas my TV does receive SmartThings notifications from for example my Smart washer).

So, my question is: is there more documentation available for the notifications API, other than is already available in the current SDK source code? T

Docs might help diagnose why I won't receive notifications on my TV.

Autocomplete is broken in callbacks

Describe the bug
Developer experience suffers greatly when configuring pages and sections because there is no "autocomplete". I am not experienced enough to know why the autocomplete (at least, in VScode) does not show the functions available to the developer. Perhaps it is because they are part of the prototype and something unique must be done to elevate these to the editor's intellisense.

To Reproduce
Steps to reproduce the behavior:

  1. Create a configuration page
  2. type page. and notice there is nothing of value in autocomplete
  3. continue to type page.section( and you will now see that autocomplete knows what this function is

Expected behavior
Autocomplete reveals available functions right at the top of intellisense/autocomplete

Actual behavior
No autocomplete is shown

Screenshots
image

Additional context
n/a

Refresh Token does not encode its body

Describe the bug
When making a request to refresh the token, the client id, secret and refreshToken are not encoded which may result in errors.

grant_type=refresh_token&client_id=${encodeURIComponent(clientId)}&client_secret=${encodeURIComponent(clientSecret)}&refresh_token=${encodeURIComponent(refreshToken)}

Device API's mentions of getState() is inconsistent

Is your feature request related to a problem? Please describe.
The actual API refers to status, but the SDK calls it state, which could (for all intents and purposes) be an entirely different feature.

Describe the solution you'd like
Rename "device state" mentions and function names to "status".

Describe alternatives you've considered
~~

Additional context
https://github.com/SmartThingsCommunity/smartapp-sdk-nodejs/blob/master/lib/platform/devices.js#L77-L79

https://github.com/SmartThingsCommunity/smartapp-sdk-nodejs/blob/master/lib/platform/devices.js#L81-L83

https://github.com/SmartThingsCommunity/smartapp-sdk-nodejs/blob/master/lib/platform/devices.js#L85-L87

appId sometimes required

Describe the bug

However, as separate issue also needed to add the .appId("string") when using .permission(...) Without this Mobile App will report the generic "Network Error has occurred" when attempting the Configuration Lifecycle.

What is interesting is I put a random value here for .appId("random") and it still worked, so unsure this functionality and purpose.

  .appId('nodejs-sdk-example')
  .publicKey('@./smartthings_rsa.pub') // optional until app verified
  .configureI18n()
  .permissions(['i:deviceprofiles'])
  .page('mainPage', (context, page, configData) => {

To Reproduce
Steps to reproduce the behavior:

  1. Create smartapp
  2. do not set appId()
  3. set permissions()
  4. Have configuration pages
  5. Try to use the configuration in oneapp
  6. See error

Expected behavior
Not setting appId() should not break the app by setting a default name

Actual behavior
Mobile app breaks when not setting it

Screenshots
n/a

Environment (please complete the following information):
n/a

Additional context
reported by @jonathanpaek, please comment if I got anything wrong here

Add jsdocs

Is your feature request related to a problem? Please describe.
It's too confusing to see functions and have no idea how to use them.

Describe the solution you'd like
use jsdoc comments above functions

Describe alternatives you've considered
generating from swagger spec would not be possible because of how custom these functions are

Additional context
n/a

i18n for defaultValue of TextSetting

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

-> I'm developing a smartapp using this SDK. I'm trying to apply localization in my app.
I tried to set default value of text setting applying i18n, but it doesn't seem to work.

Describe the solution you'd like
A clear and concise description of what you want to happen.

-> add some code in the constructor of TextSetting

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

-> or just do not support i18n. Show the same default message regardless of users' launguage setting.

Additional context
Add any other context or screenshots about the feature request here.

->
this._defaultValue = this.i18nKey('defaultValue')

instanceof cannot check types in js

In schedules.js, sdk is checking types using instanceof but it is not working in JS.
It could be improved to use isDate or isString from underscore module.

runDaily(name, time) {
	let date
	if (Array.isArray(time) && time[0].stringConfig) {
		date = new Date(time[0].stringConfig.value)
	} else if (time instanceof String) {
		date = new Date(time)
	} else if (time instanceof Date) {
		date = time

Typo in readme

Describe the bug
Typo in "highlights" section: evebnts

To Reproduce
n/a

Expected behavior
n/a

Actual behavior
n/a

Screenshots
n/a

Environment (please complete the following information):
n/a

Additional context
n/a

Config value retrieval too naive

Is your feature request related to a problem? Please describe.
util/endpoint-context.js includes several "config value" retrieval methods, which are very handy, but they only pull out the first value in the array. For arrays larger than 1, you are out of luck.

Describe the solution you'd like
I have thought of several use cases that these needs to support:

1. Raw data
  • you are building a config page and you want a list of the raw values in an array
  • e.g., deviceId, or modeId, etc.
2. Building strings with label/name
  • you are building a config page and you want to use the device Ids as a template
    • e.g., setting the description as Light Bulb 1, Light Bulb 2, Rear door lock
  • this cannot be done because you have no API access token to look up the labels, so Strongman can replace values
  • Strongman sees the literal string {{deviceId:81e3bbf5-b63e-4c18-af21-51f72520fe40}} and it will transform it into Light Bulb 1

IMHO, the responsibility of the function should be to:

  1. Return a singular string value, or to return an array of strings
  2. Not assume the user wants to do anything in particular with those strings
  3. Take flexible arguments:
  • you pass in a string argument, it finds that value and returns the un-templated value
  • you pass in an object like { name: 'modes', templated: true }, then it returns the templated value (it would also respect if you sent false)

Proposed usage

context.configStringValue('modes')

// returns 
// [ 
//    '81e3bbf5-b63e-4c18-af21-51f72520fe40', 
//    '26c972d5-40f5-413c-82d9-46da38f01a47' 
// ]
context.configStringValue({name: 'modes', templated: true})

// returns 
// [ 
//    '{{modeId:81e3bbf5-b63e-4c18-af21-51f72520fe40}}', 
//    '{{modeId:26c972d5-40f5-413c-82d9-46da38f01a47}}' 
// ]

Describe alternatives you've considered
n/a

Additional context
n/a

context.configTimeString() error

Describe the bug
When looking to obtain a time value stored in the config context which doesn't exist, an error is thrown because configTimeString() assumes that the function call to configDateValue() will return a meaningful value. Instead, it returns null.

To Reproduce
Steps to reproduce the behavior:

  1. Look up a time config value with context.configTimeString('key-that-doesn't-exist')
  2. Error is thrown in endpoint-context.js#L113 because configDateValue(key) returns null.

Expected behavior
Should safely pass through these functions without errors if the config key doesn't exist yet

Actual behavior
Throws an error and execution halts

Screenshots
n/a

Page defaults settings to all be required

Describe the bug
By default, lib/pages/Page.js sets all "Settings" to be required. This feels like it should be an "opt-in" feature instead of "opt-out", as it will throw the required: true field onto every single setting.

This had me stuck for a while because I could not progress ("Next" button was disabled) for a simple page that only included a ParagraphSetting which has no input.

To Reproduce
Steps to reproduce the behavior:

  1. Create 2 configuration Pages
  2. First page should include only a non-input setting like ParagraphSetting
  3. Define the next page id so it shows the button
  4. Notice that the Next button is disabled
  5. Set defaultRequired(false) on all pages and it works

Expected behavior
This should be opt-in, so by default it does not add the required: true field to pages. Developers can then opt-in to this feature on a page-by-page basis.

Actual behavior
All pages include required: true which can easily prevent progression.

Screenshots
n/a

Additional context
n/a

`TypeError: Cannot read property 'get' of undefined`

Describe the bug

With 1.5.0, I'm getting TypeError: Cannot read property 'get' of undefined when configuring the app without a context store.

To Reproduce
Steps to reproduce the behavior:

  1. Run the app with a configuration specified, but no context store.
  2. Try configuring an installed automation or installing the automation.

Expected behavior
The configuration screen is displayed.

Actual behavior
We send a 500 with the error above and the following stack trace:

2019-07-23T03:23:29.787Z error: TypeError: Cannot read property 'get' of undefined
    at EndpointContext.retrieveTokens (.../node_modules/@smartthings/smartapp/lib/util/endpoint-context.js:104:40)                                                            
    at SmartApp._handleCallback (.../node_modules/@smartthings/smartapp/lib/smart-app.js:598:22)                                                                              
    at SmartApp.handleHttpCallback (.../node_modules/@smartthings/smartapp/lib/smart-app.js:505:16)                                                                           
    at processTicksAndRejections (internal/process/task_queues.js:89:5)
2019-07-23T03:23:29.788Z debug: RESPONSE: {
  "statusCode": 500,
  "message": "Server error: 'TypeError: Cannot read property 'get' of undefined'"

Screenshots
N/A

Environment (please complete the following information):

  • OS: macOS 10.14.5
  • Node engine: 12.4.0

Additional context
N/A

include tooling to facilitate proper commit messages

Is your feature request related to a problem? Please describe.
We have added semantic versioning and now require commit messages to be properly formed.

Describe the solution you'd like
Include a tool like semantic-git-commit-cli to help generate properly-formed commit messages.

Describe alternatives you've considered
We could update the commit messages for the committer on merge.

Additional context
none

Missing delete method in apps.js

Description
There is no way to delete an app as Delete api request method is missing from client lib (lib/platform/app.js)

Describe the solution you'd like

delete(appId ) {
return this.st.client.request (`/apps/${id}`, 'DELETE')
}

Expected behavior
Deletes the app.

Screenshots
N/A

Environment (please complete the following information):
N/A

Add default page handler

Is your feature request related to a problem? Please describe.
If you miss handling a pageId, the configuration response will fail ungracefully.

Describe the solution you'd like
Create a default page handler that ensures a page response.

Describe alternatives you've considered
n/a

Additional context
Could add actual page content to aid in debugging.

Starter: AWS serverless support

Is your feature request related to a problem? Please describe.
When developing, it can be difficult to switch between local and cloud deployments.

Describe the solution you'd like
It would be nice to add some means to easily deploy to cloud infrastructure. Many configurations could be available which would auto-implement the basics and ask for AWS keys/secrets during the wizard.

Describe alternatives you've considered
n/a

Additional context
n/a

Unused / outdated dependencies

unused
npm-check indicates that ajv and mockery are unused. Although they are just devDependencies, this project should not include dependencies it doesn't use. If and when they are necessary, we can add them.

outdated
Logging module winston can be updated from 3.1.0 to 3.2.1. Should be a painless update.

client details sometimes missing from UPDATE event

Describe the bug
It was reported from Kidong Cho that occasionally, upon updating the application (hitting allow authorization page) the mobile app will send the event to the smartapp lacking the client object in the ExecutionRequest. The code in

this.locale = data.client.language
depends on this to be there or the event fails.

To Reproduce
I was not able to reproduce this myself, on android 1.7.37-9...

Steps to reproduce the behavior:

  1. Use android client 1.7.36-23
  2. Install / update an installedapp that uses the sdk
  3. Error is thrown at the above line of code because data.client is undefined.

Expected behavior
It doesn't throw any errors.

Actual behavior
An error is thrown and execution stops.

Additional context
Android 1.7.36-23

403 when creating device

Describe the bug
403 when creating a device through API context

To Reproduce
Steps to reproduce the behavior:
context.api.devices.create(...)

Expected behavior
200 OK Device response

Actual behavior

error: {
  "statusCode": 403,
  "request": {
    "url": "https://api.smartthings.com/devices",
    "method": "POST",
    "json": true,
    "headers": {
      "Content-Type": "application/json; charset=utf-8",
      "Authorization": "Bearer <snip>-cd5b4c674d59"
    },
    "body": {
      "label": "SDK test device",
      "locationId": "<snip>-ed130f0308f8",
      "app": {
        "profileId": "<snip>-ea99edb600ff",
        "installedAppId": "<snip>-b1a9-b639e4f629bb",
        "externalId": "ex-0001"
      }
    }
  },
  "message": "403 - undefined"
}

Screenshots
If applicable, add screenshots to help explain your problem.

Environment (please complete the following information):

  • @smartthings/smartapp": "^1.0.1

Additional context
stakeholder: @jonathanpaek

Package defines too strict of supported node engines

Currently:

"engines": {
  "node": ">=6.12.3 <8.0.0 || >=8.9.4 <10.0.0 || >=10.4.1"
}

With package managers that respect the engines field (yarn, and [email protected]), this will fail to install if your Node version is not matched here. For me, that was 10.4.0 which comes built-in to macOS.

After speaking with Bob, it is agreed that there's no real need for it to be as strict as this, but there is a floor we must obey to get the lambda expressions. 8.9.4 seems most suitable for us.

`.uninstall()` function not called after uninstalling smartapp

Describe the bug
When uninstalling() the smartapp the .uninstalled() callback when using sdk is not called.

To Reproduce

  1. Include in your SmartApp the .uninstalled() function
    ...
    .installed(() => {
        console.log("installed");
    })
    .uninstalled(() => {
        console.log("uninstalled");
    })
   ...
  1. Using the SmartThings mobile app to install and then uninstall an automation. The callback for .uninstalled() is not called.

Observed output available for installed(()=> { ... } when installing, however not uninstalled(()=> {...} after uninstalling.

Expected behavior
Callback body for `.uninstalled(()=> { ... }) called after smartapp is uninstalled.

Actual behavior
Callback body for `.uninstalled(()=> { ... }) is not called after smartapp is uninstalled.

Screenshots
n/a

Environment (please complete the following information):
n/a

Additional context

Split API from SmartApp SDK

Is your feature request related to a problem? Please describe.
As a developer of a non-SmartApp related project, I want to easily make API calls without using the smartapp sdk.

Describe the solution you'd like
Create a standalone NPM package that makes API calls only.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
The outcome (standalone project) can be consumed by this project in turn. This can stabilize and simplify this project.

Improve configuration to avoid API errors

Is your feature request related to a problem? Please describe.
In #23, we see a confusing behavior with varying configurations of smartapp.permissions(perms) and smartapp.appId(val). Configuration (💪man) has unique requirements that, until recently, weren't really well-recognized. When you set explicit permissions, you also need an appId. Otherwise it's technically not necessary.

Describe the solution you'd like
Re-work how the smartapp configuration works. "Builder pattern"/"fluent interface" is nice for some things but it limits how we can validate the smartapp's configuration at startup time. We need to ensure that we have values set - if permissions are applied, the appId has to be set, otherwise configuration will fail. So we can throw errors at runtime.

Utilize the existing constructor options object and get rid of appId(), permissions(), etc. Those should not change after the instance construction for the sake of validation.

Describe alternatives you've considered
n/a

Additional context
n/a

Inject whether or not the configuration page is a resubmitted page

Is your feature request related to a problem? Please describe.
Configuration pages can be resubmitted when a picked value changes, and that context may be required to change the outputted configuration page.

Describe the solution you'd like
Inject the comparison of pageId and previousPageId into the configuration context

Describe alternatives you've considered
n/a

Additional context
n/a

Paragraph description is missing

Describe the bug
The page setting type of paragraph is missing the description field. It is on the most base setting type so it should be universally available.

To Reproduce
Steps to reproduce the behavior:

  1. Try to make a paragraphSetting.description(...) and it will fail

Expected behavior
Description exists

Actual behavior
It doesn't

Additional context
Add any other context about the problem here.

More flexible API requests

Is your feature request related to a problem? Please describe.
I can't use async-await syntax with API calls.

Describe the solution you'd like
To avoid 'callback hell', I would like to be able to use code like this:

 try {
      const { data: unsub } = await ctx.api.subscriptions.unsubscribeAll();
      console.log(`Status: ${unsub.status}`)
      const { data: sub } = await ctx.api.subscriptions.subscribeToDevices(ctx.config.humiditySensors, "relativeHumidityMeasurement", "humidity", "humidityHandler");
      console.log(`Status: ${sub.status}`)
    } catch (err) {
      throw new Error(err)
    }

Describe alternatives you've considered
N/A

Additional context
It's not a cut-and-dry problem as there has been considerable thought put into making requests sequentially to abstract away complexities.

Starter kit of some kind is needed

Is your feature request related to a problem? Please describe.
No – the project could use a starter kit to get people up and running easily.

Describe the solution you'd like
I am thinking something like npx create-smartapp – see docs on npx. It could create a project and walk the user through the customization process.

Describe alternatives you've considered
A fork-able repository that offers a nearly-complete smartapp.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.