Skip to content

Commit

Permalink
Supports Electron 19+ (#398)
Browse files Browse the repository at this point in the history
* setup package to include dist files

* sdk as dependency instead of devDependency

* update missing dependency

* WIP refactor for v20+

* wip update build script

* update sample and readme

* update read me

* EOF

* update electron readme

* setup CI to run electron sample

* setup electron run command

* setup build script

* fix electron command

* fix arguments & readme

* fix ci argument

* add test log

* add more logs for ci debug

* log more details to debug in ci

* set ci action in create window

* set linux sample & test macos by directly run the electron sample

* test xvfb

* fix yaml

* try catch subprocess exception

* test run with check flag

* test with timeout

* improve docs

* reduce timeout time

* merge main

* merge main

* fix merge

* update aws-crt

* lock file

* update electron version to address dependabot alerts

* fix package-lock
  • Loading branch information
xiazhvera authored Feb 12, 2024
1 parent be4c213 commit 4ba0afa
Show file tree
Hide file tree
Showing 9 changed files with 426 additions and 297 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ jobs:
- name: run PubSub sample
run: |
python ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_pubsub_cfg.json
# The electron app in CI would not perform any IoT operations. The CI only verify the app is launched correctly.
# This is because electron requires manually input for pwd while processing the credentials. Currently we don't have a good way to workaround it.
- name: run PubSub Electron sample
run: |
python ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_pubsub_electron_cfg.json
# TODO Temporarily disable this job. Enable it after the issue is properly fixes.
# - name: run Windows Certificate Connect sample
# run: |
Expand Down Expand Up @@ -128,6 +133,12 @@ jobs:
- name: run PubSub sample
run: |
python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_pubsub_cfg.json
# The electron app in CI would not perform any IoT operations. The CI only verify the app is launched correctly.
# This is because electron requires manually input for pwd while processing the credentials. Currently we don't have a good way to workaround it.
- name: run PubSub Electron sample
run: |
python ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_pubsub_electron_cfg.json
- name: run PKCS12 sample
run: |
cert=$(aws secretsmanager get-secret-value --region us-east-1 --secret-id "ci/PubSub/cert" --query "SecretString" | cut -f2 -d":" | cut -f2 -d\") && echo -e "$cert" > /tmp/certificate.pem
Expand Down Expand Up @@ -166,6 +177,13 @@ jobs:
- name: run PubSub sample
run: |
python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_pubsub_cfg.json
# Electron requires X11 on linux, while github actions does not have X11 support. We work around it using XVFB.
- name: run PubSub Electron sample
run: |
export DISPLAY=:99
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
python ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_pubsub_electron_cfg.json
- name: configure AWS credentials (Device Advisor)
uses: aws-actions/configure-aws-credentials@v1
with:
Expand Down Expand Up @@ -272,6 +290,13 @@ jobs:
- name: run PubSub sample
run: |
python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_pubsub_cfg.json
# Electron requires X11 on linux, while github actions does not have X11 support. We work around it using XVFB.
- name: run PubSub Electron sample
run: |
export DISPLAY=:99
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
python ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_pubsub_electron_cfg.json
- name: run PubSub JS sample
run: |
python3 ./aws-iot-device-sdk-js-v2/utils/run_in_ci.py --file ./aws-iot-device-sdk-js-v2/.github/workflows/ci_run_pubsub_js_cfg.json
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/ci_run_pubsub_electron_cfg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"language": "Javascript",
"runnable_file": "./aws-iot-device-sdk-js-v2/samples/node/pub_sub_electron_node",
"runnable_region": "us-east-1",
"runnable_main_class": "",
"arguments": [
{
"data": "is_ci"
}
],
"node_cmd": "npm start",
"timeout": 180
}
7 changes: 7 additions & 0 deletions documents/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* [How do debug in VSCode?](#how-do-debug-in-vscode)
* [What certificates do I need?](#what-certificates-do-i-need)
* [I would like to build a browser application and got error "Property does not exist on type 'typeof import("\<path\>/node_modules/aws-crt/dist/**native**/*")](#browser-error)
* [Vercel/pkg Support](#vercel/pkg-support)
* [I still have more questions about this sdk?](#i-still-have-more-questions-about-this-sdk)

### Where should I start?
Expand Down Expand Up @@ -110,6 +111,12 @@ To set up the path in tsconfig.json, you can add a mapping for the library modul
"aws-iot-device-sdk-v2": ["node_modules/aws-iot-device-sdk-v2/dist/browser"]
},
```
### Vercel/pkg Support

#### Uncaught Error: A dynamic link library (DLL) initialization routine failed. \\?\<library path>
The vercel/pkg is a tool to package your Node.js project into an executable that can be run on devices without Node.js installed. You can find instructions at https://github.com/vercel/pkg.
If the DLL load failure issue happened on windows with Vercel/pkg, please try the latest version. The issue should be fixed in v1.19.1.
The library `aws-iot-device-sdk-v2` depends on the native modules `aws-crt`. When vercel/pkg package the node project, it would renamed node.exe into the generated single executable. In such case, the symbols needed by native modules `aws-crt` are exported by the renamed executable instead of node.exe. In order to load native modules on Windows, the library need to install a delay-load hook to redirect the reference to use the loading executable. A windows delay load is required here.


### I still have more questions about this sdk?
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

220 changes: 217 additions & 3 deletions samples/node/pub_sub_electron_node/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
const { app, BrowserWindow} = require('electron')
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require("path")
import { mqtt5, iot } from "aws-iot-device-sdk-v2"
import { ICrtError } from "aws-crt"
import { once } from "events"
import { toUtf8 } from '@aws-sdk/util-utf8-browser'
import * as args from "./settings"

function createWindow () {
const win = new BrowserWindow({
var win: Electron.BrowserWindow
var client: mqtt5.Mqtt5Client | null;
var qos0_topic = "test/topic/qos0";
var qos1_topic = "test/topic/qos1";
const cmdline_args = process.argv;

async function createWindow() {
console.log("start creating windows");
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
Expand All @@ -15,9 +27,19 @@ function createWindow () {
})

win.loadFile('./index.html');

// Launch and exit the app if we are testing in CI.
if (cmdline_args[2] == "is_ci") {
console.log("App launched in ci, exiting...");
app.quit();
}
}

app.whenReady().then(() => {
ipcMain.handle('PubSub5MtlsStart', PubSub5MtlsStart)
ipcMain.handle('PubSub5WebsocketsStart', PubSub5WebsocketsStart)
ipcMain.handle('PubSub5Stop', PubSub5Stop)
ipcMain.handle('PublishTestMessage', PublishTestQoS1Message)
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
Expand All @@ -32,3 +54,195 @@ app.on('window-all-closed', () => {
}

})


app.on('will-quit', async () => {
await PubSub5Stop()
});

function console_render_log(msg: string) {
try {
win?.webContents?.send('log', msg)
console.log(msg);
}
catch (error) {
console.log("Failed to log the message: " + error)
}
}

function createClientConfig(isWebsocket: boolean): mqtt5.Mqtt5ClientConfig {
let builder: iot.AwsIotMqtt5ClientConfigBuilder | undefined = undefined;

if (!isWebsocket) {
console_render_log("Start to build client with Mtls... Please make sure setting up the credentials in \"Settings.ts\"");
builder = iot.AwsIotMqtt5ClientConfigBuilder.newDirectMqttBuilderWithMtlsFromPath(
args.endpoint,
args.cert_file_path,
args.key_file_path
);
} else {
console_render_log("Start to build client with websocket configuration... Please make sure setup the endpoint and region in \"Settings.ts\"");
let wsOptions: iot.WebsocketSigv4Config | undefined = undefined;
if (args.region) {
wsOptions = { region: args.region };
}
builder = iot.AwsIotMqtt5ClientConfigBuilder.newWebsocketMqttBuilderWithSigv4Auth(
args.endpoint,
wsOptions
);
}

builder.withConnectProperties({
keepAliveIntervalSeconds: 1200,
clientId: "test-client"
});

return builder.build();
}


function createClient(isWebsocket: boolean): mqtt5.Mqtt5Client {

let config: mqtt5.Mqtt5ClientConfig = createClientConfig(isWebsocket);

console_render_log("Creating client for " + config.hostName);
client = new mqtt5.Mqtt5Client(config);

client.on('error', (error: ICrtError) => {
console_render_log("Error event: " + error.toString());
});

client.on("messageReceived", (eventData: mqtt5.MessageReceivedEvent): void => {
console_render_log("Message Received event: " + JSON.stringify(eventData.message));
if (eventData.message.payload) {
console_render_log(" with payload: " + toUtf8(new Uint8Array(eventData.message.payload as ArrayBuffer)));
}
});

client.on('attemptingConnect', (eventData: mqtt5.AttemptingConnectEvent) => {
console_render_log("Attempting Connect event");
});

client.on('connectionSuccess', (eventData: mqtt5.ConnectionSuccessEvent) => {
console_render_log("Connection Success event");
console_render_log("Connack: " + JSON.stringify(eventData.connack));
console_render_log("Settings: " + JSON.stringify(eventData.settings));
});

client.on('connectionFailure', (eventData: mqtt5.ConnectionFailureEvent) => {
console_render_log("Connection failure event: " + eventData.error.toString());
if (eventData.connack) {
console_render_log("Connack: " + JSON.stringify(eventData.connack));
}
});

client.on('disconnection', (eventData: mqtt5.DisconnectionEvent) => {
console_render_log("Disconnection event: " + eventData.error.toString());
if (eventData.disconnect !== undefined) {
console_render_log('Disconnect packet: ' + JSON.stringify(eventData.disconnect));
}
});

client.on('stopped', (eventData: mqtt5.StoppedEvent) => {
console_render_log("Stopped event");
});

return client;
}

async function createClientAndStartPubSub(isWebsocket: boolean) {

try {

client = createClient(isWebsocket);

const connectionSuccess = once(client, "connectionSuccess");

client.start();
await connectionSuccess;

const suback = await client.subscribe({
subscriptions: [
{ qos: mqtt5.QoS.AtLeastOnce, topicFilter: qos1_topic },
{ qos: mqtt5.QoS.AtMostOnce, topicFilter: qos0_topic }
]
});
console_render_log('Suback result: ' + JSON.stringify(suback));

const qos0PublishResult = await client.publish({
qos: mqtt5.QoS.AtMostOnce,
topicName: qos0_topic,
payload: JSON.stringify("This is a qos 0 payload"),
userProperties: [
{ name: "test", value: "userproperty" }
]
});
console_render_log('QoS 0 Publish result: ' + JSON.stringify(qos0PublishResult));

const qos1PublishResult = await client.publish({
qos: mqtt5.QoS.AtLeastOnce,
topicName: qos1_topic,
payload: JSON.stringify("This is a qos 1 payload")
});
console_render_log('QoS 1 Publish result: ' + JSON.stringify(qos1PublishResult));
}
catch (error) {
console_render_log("Client failed: " + error)
}

}

export const PubSub5MtlsStart = async () => {
if (client != null) {
console_render_log("Client is already started.");
return;
}

await createClientAndStartPubSub(false)

}

export const PubSub5WebsocketsStart = async () => {
if (client != null) {
console_render_log("Client is already started, please stop the client first.");
return;
}

await createClientAndStartPubSub(true);

}


async function PubSub5Stop() {
if (client == null) {
console_render_log("Client is not started.")
return;
}

try {
const stopped = once(client, "stopped");
client.stop();
await stopped;
client.close();
client = null;
}
catch (error) {
console_render_log("Client is not started.")
}
}



async function PublishTestQoS1Message() {
if (client == null) {
console_render_log("Client is not started.")
return;
}

const qos1PublishResult = await client.publish({
qos: mqtt5.QoS.AtLeastOnce,
topicName: qos1_topic,
payload: JSON.stringify("This is a qos 1 payload")
});
console_render_log('QoS 1 Publish result: ' + JSON.stringify(qos1PublishResult));
}
Loading

0 comments on commit 4ba0afa

Please sign in to comment.