{"id":18695335,"url":"https://github.com/smartthingscommunity/st-schema-nodejs","last_synced_at":"2025-08-08T13:14:06.680Z","repository":{"id":33852496,"uuid":"156147395","full_name":"SmartThingsCommunity/st-schema-nodejs","owner":"SmartThingsCommunity","description":"ST Schema helper library for NodeJS","archived":false,"fork":false,"pushed_at":"2023-06-08T23:54:17.000Z","size":326,"stargazers_count":36,"open_issues_count":7,"forks_count":36,"subscribers_count":93,"default_branch":"master","last_synced_at":"2024-12-09T12:25:49.461Z","etag":null,"topics":["c2c","iot","sdk-nodejs","smartthings"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SmartThingsCommunity.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-05T02:08:50.000Z","updated_at":"2024-07-18T15:08:55.000Z","dependencies_parsed_at":"2024-09-18T19:22:27.718Z","dependency_job_id":null,"html_url":"https://github.com/SmartThingsCommunity/st-schema-nodejs","commit_stats":{"total_commits":43,"total_committers":12,"mean_commits":"3.5833333333333335","dds":0.6976744186046512,"last_synced_commit":"67833d473c145ab36f6e674a08c1a6053420e7a5"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmartThingsCommunity%2Fst-schema-nodejs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmartThingsCommunity%2Fst-schema-nodejs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmartThingsCommunity%2Fst-schema-nodejs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmartThingsCommunity%2Fst-schema-nodejs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SmartThingsCommunity","download_url":"https://codeload.github.com/SmartThingsCommunity/st-schema-nodejs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230547678,"owners_count":18243227,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c2c","iot","sdk-nodejs","smartthings"],"created_at":"2024-11-07T11:14:40.435Z","updated_at":"2024-12-20T07:07:04.155Z","avatar_url":"https://github.com/SmartThingsCommunity.png","language":"JavaScript","readme":"# st-schema-nodejs\nST Schema helper library for NodeJS\n\n## Installing the module\n```\nnpm install st-schema\n```\n\n## Connector app structure\n```javascript\nconst connector = new SchemaConnector()\n  .discoveryHandler((accessToken, response) =\u003e {\n    /**\n     * Discovery request. Respond with a list of devices. Called after installation of the\n     * connector and every six hours after that.\n     * @accessToken External cloud access token\n     * @response {DiscoveryResponse} Discovery response object\n     */\n  })\n  .stateRefreshHandler((accessToken, response) =\u003e {\n    /**\n     * State refresh request. Respond with the current states of all devices. Called after\n     * device discovery runs.\n     * @accessToken External cloud access token\n     * @response {StateRefreshResponse} StateRefresh response object\n     */\n  })\n  .commandHandler((accessToken, response, devices) =\u003e {\n    /**\n     * Device command request. Control the devices and respond with new device states\n     * @accessToken External cloud access token\n     * @response {CommandResponse} CommandResponse response object\n     * @devices {array} List of ST device commands\n     */\n  })\n  .callbackAccessHandler((accessToken, callbackAuthentication, callbackUrls) =\u003e {\n    /**\n     * Create access and refresh tokens to allow SmartThings to be informed of device state\n     * changes as they happen. \n     * @accessToken External cloud access token\n     * @callbackAuthentication ST access and refresh tokens for proactive state callbacks\n     * @callbackUrls Callback and refresh token URLs\n     */\n  })\n  .integrationDeletedHandler(accessToken =\u003e {\n    /**\n     * Called when the connector is removed from SmartThings. You may want clean up access\n     * tokens and other data when that happend.\n     * @accessToken External cloud access token\n     */\n  });\n\n```\n\n## Minimal loopback connector example\nThis simple connector creates a one dimmer device named _Test Dimmer_. There's no physical\ndevice involved. The connector command handler simply returns the state value corresponding to \nthe issued command. The current state of the device is stored in memory, so if the server\nis restarted the states will revert to their initial value. This implementation does not \nimplement proactive state callbacks.\n\n#### connector.js\n```javascript\nconst {SchemaConnector, DeviceErrorTypes} = require('st-schema')\nconst deviceStates = { switch: 'off', level: 100}\nconst connector = new SchemaConnector()\n  .discoveryHandler((accessToken, response) =\u003e {\n    response.addDevice('external-device-1', 'Test Dimmer', 'c2c-dimmer')\n      .manufacturerName('Example Connector')\n      .modelName('Virtual Dimmer');\n  })\n  .stateRefreshHandler((accessToken, response) =\u003e {\n    response.addDevice('external-device-1', [\n      {\n        component: 'main',\n        capability: 'st.switch',\n        attribute: 'switch',\n        value: deviceStates.switch\n      },\n      {\n        component: 'main',\n        capability: 'st.switchLevel',\n        attribute: 'level',\n        value: deviceStates.level\n      }\n    ])\n  })\n  .commandHandler((accessToken, response, devices) =\u003e {\n    for (const device of devices) {\n      const deviceResponse = response.addDevice(device.externalDeviceId);\n      for (cmd of device.commands) {\n        const state = {\n          component: cmd.component,\n          capability: cmd.capability\n        };\n        if (cmd.capability === 'st.switchLevel' \u0026\u0026 cmd.command === 'setLevel') {\n          state.attribute = 'level';\n          state.value = deviceStates.level = cmd.arguments[0];\n          deviceResponse.addState(state);\n\n        } else if (cmd.capability === 'st.switch') {\n          state.attribute = 'switch';\n          state.value = deviceStates.switch = cmd.command === 'on' ? 'on' : 'off';\n          deviceResponse.addState(state);\n\n        } else {\n          deviceResponse.setError(\n            `Command '${cmd.command} of capability '${cmd.capability}' not supported`,\n            DeviceErrorTypes.CAPABILITY_NOT_SUPPORTED)\n        }\n      }\n    }\n  });\n\nmodule.exports = connector\n```\n  \n## Running as a web-service\nTo run the above connector as a web service using the _Express_ framework create a server\nlike this one. Note that a real application would need to validate the access token \npassed in each request. This example only checks for the presence of the token.\n\n#### server.js\n```javascript\nconst express = require('express');\nconst connector = require('./connector');\nconst server = express();\nconst port = 3000;\nserver.use(express.json());\n\nserver.post('/', (req, res) =\u003e {\n  if (accessTokenIsValid(req)) {\n    connector.handleHttpCallback(req, res)\n  }\n});\n\nfunction accessTokenIsValid(req) {\n  // Replace with proper validation of issued access token\n  if (req.body.authentication.token) {\n    return true;\n  }\n  res.status(401).send('Unauthorized')\n  return false;\n}\n\nserver.listen(port);\nconsole.log(`Server listening on http://127.0.0.1:${port}`);\n```\n\n## Running as an AWS Lambda\n\nTo run the connector as an AWS lambda use a handler like this one.\n\n#### index.js\n```javascript\nconst connector = require('./connector');\nexports.handle = async (evt, context, callback) =\u003e {\n    return connector.handleLambdaCallback(evt, context, callback);\n};\n```\n\n## Proactive state callbacks\n\nSensors and devices that can be controlled other than through the SmartThings mobile app can change state at any time.\nTo ensure that the SmartThings platform is made aware of these state changes right away callsbacks can be implemented\nto call into the SmartThings cloud. These callbacks are secured via a token exchange dependent on the client ID\nand secret defined for the ST Schema connector in the Developer Workspace. The following example is a\nminimal implementation of a connector that supports these callback. It builds on the previous example by implementing\nthe callbacks and exposing a web-service endpoint for executing device commands.\n\n#### app.js\n\nThe connector app is now initialized with the ST Schema connector's client ID and secret, which are available from\nthe Developer workspace. It also declares an `accessTokens` map to contain the list of connectors that need to be\ncalled when device state changes. Note that this simple implementation stores the connectors in memory, so restarting\nthe server will cause them to be lost. The app also has new `callbackAccessHandler` and `integrationDeletedHandler`\nhandlers defined to add and remove entries from the `accessTokens` map.\n```javascript\nconst {SchemaConnector} = require('st-schema');\nconst deviceStates = {switch: 'off', level: 100};\nconst accessTokens = {};\nconst connector = new SchemaConnector()\n  .clientId(process.env.ST_CLIENT_ID)\n  .clientSecret(process.env.ST_CLIENT_SECRET)\n  .discoveryHandler((accessToken, response) =\u003e {\n    response.addDevice('external-device-1', 'Test Dimmer', 'c2c-dimmer')\n      .manufacturerName('Example Connector')\n      .modelName('Virtual Dimmer');\n  })\n  .stateRefreshHandler((accessToken, response) =\u003e {\n    response.addDevice('external-device-1', [\n      {\n        component: 'main',\n        capability: 'st.switch',\n        attribute: 'switch',\n        value: deviceStates.switch\n      },\n      {\n        component: 'main',\n        capability: 'st.switchLevel',\n        attribute: 'level',\n        value: deviceStates.level\n      }\n    ])\n  })\n  .commandHandler((accessToken, response, devices) =\u003e {\n    for (const device of devices) {\n      const deviceResponse = response.addDevice(device.externalDeviceId);\n      for (cmd of device.commands) {\n        const state = {\n          component: cmd.component,\n          capability: cmd.capability\n        };\n        if (cmd.capability === 'st.switchLevel' \u0026\u0026 cmd.command === 'setLevel') {\n          state.attribute = 'level';\n          state.value = deviceStates.level = cmd.arguments[0];\n          deviceResponse.addState(state);\n\n        } else if (cmd.capability === 'st.switch') {\n          state.attribute = 'switch';\n          state.value = deviceStates.switch = cmd.command === 'on' ? 'on' : 'off';\n          deviceResponse.addState(state);\n\n        } else {\n          deviceResponse.setError(\n            `Command '${cmd.command} of capability '${cmd.capability}' not supported`,\n            DeviceErrorTypes.CAPABILITY_NOT_SUPPORTED)\n        }\n      }\n    }\n  })\n  .callbackAccessHandler((accessToken, callbackAuthentication, callbackUrls) =\u003e {\n    accessTokens[accessToken] = {\n      callbackAuthentication,\n      callbackUrls\n    }\n  })\n\n  .integrationDeletedHandler(accessToken =\u003e {\n    delete accessTokens[accessToken]\n  });\n\nmodule.exports = {\n  connector: connector,\n  deviceStates: deviceStates,\n  accessTokens: accessTokens\n};\n\n```\n\n#### server.js\n\nThe web server is modified to add a new `/command` endpoint for turning on and off the switch. It expects\na JSON body of the form `{\"attribute\": \"switch\", \"value\": \"on\"}`. \n\n```javascript\n\"use strict\";\nrequire('dotenv').config();\nconst express = require('express');\nconst {StateUpdateRequest} = require('st-schema');\nconst {connector, deviceStates, accessTokens} = require('./app');\nconst server = express();\nconst port = 3001;\nserver.use(express.json());\n\nserver.post('/', (req, res) =\u003e {\n  if (accessTokenIsValid(req)) {\n    connector.handleHttpCallback(req, res)\n  }\n});\n\nserver.post('/command', (req, res) =\u003e {\n  deviceStates[req.body.attribute] = req.body.value;\n  for (const accessToken of Object.keys(accessTokens)) {\n    const item = accessTokens[accessToken];\n    const updateRequest = new StateUpdateRequest(process.env.ST_CLIENT_ID, process.env.ST_CLIENT_SECRET);\n    const deviceState = [\n      {\n        externalDeviceId: 'external-device-1',\n        states: [\n          {\n            component: 'main',\n            capability: req.body.attribute === 'level' ? 'st.switchLevel' : 'st.switch',\n            attribute: req.body.attribute,\n            value: req.body.value\n          }\n        ]\n      }\n    ];\n    updateRequest.updateState(item.callbackUrls, item.callbackAuthentication, deviceState)\n  }\n  res.send({});\n  res.end()\n});\n\n\nfunction accessTokenIsValid(req) {\n  // Replace with proper validation of issued access token\n  if (req.body.authentication \u0026\u0026 req.body.authentication.token) {\n    return true;\n  }\n  res.status(401).send('Unauthorized');\n  return false;\n}\n\nserver.listen(port);\nconsole.log(`Server listening on http://127.0.0.1:${port}`);\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmartthingscommunity%2Fst-schema-nodejs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmartthingscommunity%2Fst-schema-nodejs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmartthingscommunity%2Fst-schema-nodejs/lists"}