Search code examples
amazon-web-servicesaws-lambdaalexa-skills-kit

Run Amazon Alexa Skill locally instead of AWS Lambda (Javascript)


Is it possible to run alexa skill locally with ngrok instead AWS? I built a skill in AWS Lambda but I would rather use my own server. What can I do to run Alexa locally?

I tried https://github.com/alexa-js/alexa-app-server but it makes any sense because I would need to rewrite my whole code :( The better solution is http://docs.bespoken.tools/en/latest/tutorials/tutorial_lambda_nodejs/ but it isn't the best. It just works only for a wellcome intent and freezes after that :(

Terminal Logs from bespken command:

    BST: v0.9.35  Node: v7.8.0

Your URL for Alexa Skill configuration:
https://proxy.bespoken.tools?node-id=33efccba-2246-477f-bbb8-2e1e510cce9d

INFO  2017-04-25T20:27:20.628Z Connected - proxy.bespoken.tools:5000
INFO  2017-04-25T20:27:26.812Z RequestReceived: POST /?node-id=33efccba-2246-477f-bbb8-2e1e510cce9d ID: 1493152039146
INFO  2017-04-25T20:27:26.815Z Forwarding localhost:10000
Current hour: 24
Warning: Application ID is not set
INFO  2017-04-25T20:27:27.939Z ResponseReceived ID: 1493152039146
INFO  2017-04-25T20:28:10.755Z RequestReceived: POST /?node-id=33efccba-2246-477f-bbb8-2e1e510cce9d ID: 1493152078963
INFO  2017-04-25T20:28:10.756Z Forwarding localhost:10000
Warning: Application ID is not set
INFO  2017-04-25T20:28:11.157Z ResponseReceived ID: 1493152078963
INFO  2017-04-25T20:28:51.073Z RequestReceived: POST /?node-id=33efccba-2246-477f-bbb8-2e1e510cce9d ID: 1493152113739
INFO  2017-04-25T20:28:51.073Z Forwarding localhost:10000
Warning: Application ID is not set
INFO  2017-04-25T20:28:51.995Z ResponseReceived ID: 1493152113739

Solution

  • Here's some sample code that you can use to easily run a Lambda locally, call this file AlexaLambda.js:

    const log = require('console');
    var AWS = require('aws-sdk');
    
    AWS.config.region = "us-east-1";
    AWS.config.update({
        accessKeyId: "----",
        secretAccessKey: "----",
    });
    
    /**
     * Wraps the actual underlying Alexa lambda initialization in a 
     * Promise. Injects test mocks where appropriate.
     */
    var initializerPromise = new Promise(function(fulfill, reject) {
        // Mock out certain imports here if you want but not necessary
        /*
      var Module = require('module');
      var originalRequire = Module.prototype.require;
      Module.prototype.require = function() {
        if ((arguments[0] == 'S3FeedService') ||
            (arguments[0] == './lib/S3FeedService')) {
          return MockS3Service;
        } else if ((arguments[0] == 'WebsocketService') ||
                   (arguments[0] == './lib/WebsocketService')) {
          return WSMockService;
        } else if ((arguments[0] == 'SQSService') ||
                   (arguments[0] == './lib/SQSService')) {
          return SQSMockService;
        } else {
          return originalRequire.apply(this, arguments);
        }
      };*/
      // Import your actual lambda here.
      var lambda = require('../src/index.js');
      fulfill(lambda);
    
    });
    
    /**
     * The Alexa Lambda context object which is called upon completion
     * of lambda execution.  Also wraps the callback which contains the 
     * test assertion code of the caller.
     * @param callback - must be of the form function(error, result) {};
     * @returns
     */
    function Context(callback) {
        this.clientContext = {"env": {}}; 
        this.callback = callback;
    }
    
    Context.prototype.done = function(error, result) {
        if (typeof error != "undefined" && error) {
            this.callback(error, null);
        } else {
            this.callback(null, result);
        }
    }
    
    Context.prototype.succeed = function(result) {
        this.callback(null, result);
    }
    
    Context.prototype.fail = function(error) {
        this.callback(error, null);
    }
    
    /**
     * The AlexaLambda object that's exposed for test cases.
     * @returns
     */
    function AlexaLambda() {
    }
    
    /**
     * Executes the lambda function, provided an inputEvent and a 
     * callback.
     * @param inputEvent - the input event that includes the intent.
     * @param callback - called upon completion of lambda execution.
     */
    AlexaLambda.prototype.execute = function(inputEvent, callback) {
        initializerPromise.then(function(lambda) {
            var context = new Context(callback);
            lambda.handler(inputEvent, context);
        });
    }
    
    /**
     * Export the lambda class, importers instantiate via new AlexaLambda();
     */
    module.exports = AlexaLambda;
    

    Then you can use this 'AlexaLambda' in your tests like so (in my case, I'm using Mocha):

    var AlexaLambda = require('./AlexaLambda');
    var Event = require('./Event');  // My 'fake' Event class
    
    describe("Guest User Test", function() {
      var alexaLambda = new AlexaLambda();      
    
      it("Alexa, open/launch 60db", function(done) {
        var event = Event.createLaunchEvent();
        alexaLambda.execute(event, function(error, result) {
          validateYourResultHere();
          done();
        })
      });
    

    Then it's just a matter of running your test via whatever framework you're using.