UNPKG

st-schema

Version:
301 lines (259 loc) 8.84 kB
'use strict'; const DiscoveryResponse = require("./discovery/DiscoveryResponse"); const StateRefreshResponse = require("./state/StateRefreshResponse"); const CommandResponse = require("./state/CommandResponse"); const AccessTokenRequest = require("./callbacks/AccessTokenRequest"); const STBase = require('./STBase'); const GlobalErrorTypes = require('./errors/global-error-types'); const clientId = Symbol('private)'); const clientSecret = Symbol('private)'); const discoveryHandler = Symbol('private)'); const stateRefreshHandler = Symbol('private)'); const commandHandler = Symbol('private)'); const callbackTokenRequestHandler = Symbol('private)'); const callbackAccessHandler = Symbol('private)'); const integrationDeletedHandler = Symbol('private)'); const interactionResultHandler = Symbol('private)'); const enableEventLogging = Symbol('private'); const eventLoggingSpace = Symbol('private'); const handleCallback = Symbol('private'); module.exports = class SchemaConnector { constructor(options = {}) { this[clientId] = options.clientId; this[clientSecret] = options.clientSecret; this[discoveryHandler] = ((accessToken, response, data) => { console.log('discoverDevices not defined') }); this[stateRefreshHandler] = ((accessToken, response, data) => { console.log('stateRefreshHandler not defined') }); this[commandHandler] = ((accessToken, response, devices, data) => { console.log('commandHandler not defined') }); this[callbackAccessHandler] = null; this[callbackTokenRequestHandler] = (async (clientId, clientSecret, body) => { const tokenRequest = new AccessTokenRequest( clientId, clientSecret, body.headers.requestId ); return await tokenRequest.getCallbackToken( body.callbackUrls.oauthToken, body.callbackAuthentication.code ); }); this[integrationDeletedHandler] = ((accessToken, data) => { console.log('integrationDeletedHandler not defined') }); this[interactionResultHandler] = ((accessToken, data) => { if (!this[enableEventLogging]) { console.log(`INTERACTION RESULT: ${JSON.stringify(data)}`); } }) } /** * Set your smartapp automation's client id. Cannot be * acquired until your app has been created through the * Developer Workspace. * @param {String} id * @returns {SchemaConnector} SchemaConnector instance */ clientId(id) { this[clientId] = id; return this } /** * Set your smartapp automation's client secret. Cannot be * acquired until your app has been created through the * Developer Workspace. This secret should never be shared * or committed into a public repository. * @param {String} secret * @returns {SchemaConnector} SmartApp instance */ clientSecret(secret) { this[clientSecret] = secret; return this } /** * Sets the discovery request handler */ discoveryHandler(callback) { this[discoveryHandler] = callback; return this } /** * Sets the state refresh request handler */ stateRefreshHandler(callback) { this[stateRefreshHandler] = callback; return this } /** * Sets the command request handler */ commandHandler(callback) { this[commandHandler] = callback; return this } /** * Sets the handlers called after callback access is granted */ callbackAccessHandler(callback) { this[callbackAccessHandler] = callback; return this } /** * Overrides the built in handler that requests callback access tokens. * That handler is expected to return the response of the callback token request. */ callbackTokenRequestHandler(callback) { this[callbackTokenRequestHandler] = callback return this } /** * Sets integration deleted handler */ integrationDeletedHandler(callback) { this[integrationDeletedHandler] = callback; return this } /** * Sets interaction result handler */ interactionResultHandler(callback) { this[interactionResultHandler] = callback; return this } /** * Provide a custom context store used for storing in-flight credentials * for each installed instance of the app. * * @param {*} value * @example Use the AWS DynamoDB plugin * smartapp.contextStore(new DynamoDBSchemaCallbackStore('aws-region', 'app-table-name')) * @example * // Use Firebase Cloud Firestore * smartapp.contextStore(new FirestoreDBContextStore(firebaseServiceAccount, 'app-table-name')) * @returns {SchemaConnector} SmartApp instance */ callbackStore(value) { this[contextStore] = value; return this } enableEventLogging(jsonSpace = null, enableEvents = true) { this[enableEventLogging] = enableEvents; this[eventLoggingSpace] = jsonSpace; return this } /** * Use with an AWS Lambda function. No signature verification is required. * * @param {*} event * @param {*} context */ async handleLambdaCallback(event, context) { try { const response = await this[handleCallback](event); if (response.isError()) { return context.succeed(response) } else { return context.succeed(response); } } catch (err) { console.log("ERROR: %s", err.stack || err); return context.fail(err) } } /** * Use with a standard HTTP webhook endpoint app. Signature verification is required. * * @param {*} req * @param {*} res */ async handleHttpCallback(req, res) { try { const response = await this[handleCallback](req.body); if (response.isError()) { console.log("ERROR: %j", response); res.status(200).send(response) } else { res.send(response) } } catch (err) { console.log("ERROR: %s", err.stack || err); res.status(500).send(err) } } async handleCallback(body) { try { return await this[handleCallback](body) } catch (err) { return err; } } async [handleCallback](body) { if (this[enableEventLogging]) { console.log(`REQUEST ${JSON.stringify(body, null, this[eventLoggingSpace])}`) } if (!body.headers) { return new STBase().setError( `Invalid ST Schema request. No 'headers' field present.`, GlobalErrorTypes.BAD_REQUEST); } if (!body.authentication) { return new STBase().setError( `Invalid ST Schema request. No 'authentication' field present.`, GlobalErrorTypes.BAD_REQUEST); } let response; switch (body.headers.interactionType) { case "discoveryRequest": response = new DiscoveryResponse(body.headers.requestId); await this[discoveryHandler](body.authentication.token, response, body); break; case "commandRequest": response = new CommandResponse(body.headers.requestId); await this[commandHandler](body.authentication.token, response, body.devices, body); break; case "stateRefreshRequest": response = new StateRefreshResponse(body.headers.requestId); await this[stateRefreshHandler](body.authentication.token, response, body); break; case "grantCallbackAccess": response = new STBase(body.headers.interactionType, body.headers.requestId); if (this[callbackAccessHandler]) { if (body.callbackAuthentication.clientId === this[clientId] ) { const tokenResponse = await this[callbackTokenRequestHandler](this[clientId], this[clientSecret], body) await this[callbackAccessHandler](body.authentication.token, tokenResponse.callbackAuthentication, body.callbackUrls, body) } else { response = new STBase(body.headers.interactionType, body.headers.requestId) .setError(`Client ID ${body.callbackAuthentication.clientId} is invalid`, GlobalErrorTypes.INVALID_CLIENT); } } break; case "integrationDeleted": response = new STBase(body.headers.interactionType, body.headers.requestId); await this[integrationDeletedHandler](body.authentication.token, body); break; case "interactionResult": response = new STBase(body.headers.interactionType, body.headers.requestId); await this[interactionResultHandler](body.authentication.token, body); break; default: response = new STBase(body.headers.interactionType, body.headers.requestId).setError( `Unsupported interactionType: '${body.headers.interactionType}'`, GlobalErrorTypes.INVALID_INTERACTION_TYPE); break; } if (this[enableEventLogging]) { console.log(`RESPONSE ${JSON.stringify(response, null, this[eventLoggingSpace])}`) } return response; } };