TLDR: How do i send a short payload from a mqtt request to aws iot to aws lambda that has a open connection via apigateway to an electron app running locally in linux.
I have a esp8266 with the following code as the init.js This code succesfully sends it's message to aws iot, with a rule set to trigger a lambda called sendmessage. Now this sendmessage lambda is connected via websockets to a Electon app locally on my linux machine. I am able to send messages from the Electron app via websockets to api gateway wss url. I followed this example here which sets up all the websockets with api gateway and aws lambdas (one being the sendmessage lambda).
load("api_config.js");
load("api_gpio.js");
load("api_mqtt.js");
load("api_sys.js");
load("api_timer.js");
let pin = 0;
GPIO.set_button_handler(
pin,
GPIO.PULL_UP,
GPIO.INT_EDGE_NEG,
50,
function (x) {
let res = MQTT.pub(
"mOS/topic1",
JSON.stringify({ action: "sendmessage", data: "pushed" }),
1
);
print(res);
print("Published:", res ? "yes" : "no");
let connected = MQTT.isConnected();
print(connected);
},
true
);
print("Flash button is configured on GPIO pin", pin);
print("Press the flash button now!");
I know that the message from iot to sendmessage lambda needs to be a websockets message, but it only has the minimal object of {"action":"sendmessage","data":"hello world"} it's missing a bunch of information that a websocket would need. But I do not need a websocket connection between aws iot - and the sendmessage lambda, I need It to go from IOT -> sendmessage lambda with minimal payload -> electron app via websockets with payload from IOT.
SENDMESSAGE LAMBDA
// Copyright 2018-2020Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10', region: process.env.AWS_REGION });
const { TABLE_NAME } = process.env;
exports.handler = async event => {
let connectionData;
try {
connectionData = await ddb.scan({ TableName: TABLE_NAME, ProjectionExpression: 'connectionId' }).promise();
} catch (e) {
return { statusCode: 500, body: e.stack };
}
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: event.requestContext.domainName + '/' + event.requestContext.stage
});
const postData = JSON.parse(event.body).data;
const postCalls = connectionData.Items.map(async ({ connectionId }) => {
try {
await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: postData }).promise();
} catch (e) {
if (e.statusCode === 410) {
console.log(`Found stale connection, deleting ${connectionId}`);
await ddb.delete({ TableName: TABLE_NAME, Key: { connectionId } }).promise();
} else {
throw e;
}
}
});
try {
await Promise.all(postCalls);
} catch (e) {
return { statusCode: 500, body: e.stack };
}
return { statusCode: 200, body: 'Data sent.' };
};
onconnect lambda
// SPDX-License-Identifier: MIT-0
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10', region: process.env.AWS_REGION });
exports.handler = async event => {
const putParams = {
TableName: process.env.TABLE_NAME,
Item: {
connectionId: event.requestContext.connectionId
}
};
try {
await ddb.put(putParams).promise();
} catch (err) {
return { statusCode: 500, body: 'Failed to connect: ' + JSON.stringify(err) };
}
return { statusCode: 200, body: 'Connected.' };
};
ondisconnect lambda
// Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
// https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-route-keys-connect-disconnect.html
// The $disconnect route is executed after the connection is closed.
// The connection can be closed by the server or by the client. As the connection is already closed when it is executed,
// $disconnect is a best-effort event.
// API Gateway will try its best to deliver the $disconnect event to your integration, but it cannot guarantee delivery.
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10', region: process.env.AWS_REGION });
exports.handler = async event => {
const deleteParams = {
TableName: process.env.TABLE_NAME,
Key: {
connectionId: event.requestContext.connectionId
}
};
try {
await ddb.delete(deleteParams).promise();
} catch (err) {
return { statusCode: 500, body: 'Failed to disconnect: ' + JSON.stringify(err) };
}
return { statusCode: 200, body: 'Disconnected.' };
};
In my electron app I have the following code to test the websocket but I am getting a forbidden error. Howerver with wscat it works...
"use strict";
const { app, BrowserWindow } = require("electron");
const { Notification } = require("electron");
const WebSocket = require("ws");
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
},
});
win.loadFile("index.html");
win.webContents.openDevTools();
}
app.whenReady().then(createWindow);
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// Tell express to use the body-parser middleware and to not parse extended bodies
const url = "wss://random.execute-api.us-east-1.amazonaws.com/Prod";
const connection = new WebSocket(url);
connection.onopen = () => {
connection.send("hello world");
};
connection.onmessage = (e) => {
console.log(e.data);
};
connection.onerror = (error) => {
console.log(`WebSocket error: ${error}`);
};
function showNotification() {
const notification = {
title: "Basic Notification",
body: `notification`,
};
new Notification(notification).show();
}
app.whenReady().then(createWindow).then(showNotification);
I now setup my mqtt event to send the same data to the lambda but I get the following error in the lambda
{
"errorType": "TypeError",
"errorMessage": "Cannot read property 'domainName' of undefined",
"stack": [
"TypeError: Cannot read property 'domainName' of undefined",
" at Runtime.exports.handler (/var/task/app.js:29:28)",
" at processTicksAndRejections (internal/process/task_queues.js:97:5)"
]
}
Update: Here is my last lambda where I send a message to the wss address after recieving an event from IOT, but it does not work it console logs the event but doesnt fire any of the ws.on functions
// const axios = require('axios')
// const url = 'http://checkip.amazonaws.com/';
const WebSocket = require("ws");
let response;
/**
*
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
* @param {Object} event - API Gateway Lambda Proxy Input Format
*
* Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html
* @param {Object} context
*
* Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
* @returns {Object} object - API Gateway Lambda Proxy Output Format
*
*/
exports.lambdaHandler = async (event, context) => {
try {
// const ret = await axios(url);
console.log(event);
const url = "wss://obsf.execute-api.us-east-1.amazonaws.com/Prod";
const ws = new WebSocket(url);
var test = { action: "sendmessage", data: "hello world from button" };
ws.on("open", function open() {
ws.send(JSON.stringify(test));
});
ws.on("message", function incoming(data) {
console.log(data);
});
response = {
statusCode: 200,
body: JSON.stringify({
message: "hello world",
// location: ret.data.trim()
}),
};
} catch (err) {
console.log(err);
return err;
}
return response;
};
Update: Lastly I have tried this and I can't even get an error, I know ws is there because if I console it it returns a big object with a bunch of functions
console.log(ws); this returns a large object
ws.on("error", console.error); this does nothing