Correct way to use AWS SDK within AWS CDK - javascript

I'm trying to use the SDK within my CDK application. My stack is creating a directory, some networking stuff, then some instances which it joins to the domain using a PowerShell script which requires the AD DNS IPs, I'm currently using it like so:
const ds = new DirectoryService();
const result = new Promise(function (resolve: (value: string) => any, reject) {
ds.describeDirectories({}, function (err, data) {
if (err){
reject(data)
}else{
try{
if (data.DirectoryDescriptions){
if (data.DirectoryDescriptions[0].DnsIpAddrs){
resolve(data.DirectoryDescriptions[0].DnsIpAddrs.toString())
}
}
}catch (e) {
reject("Directory doesn't exist yet.")
}
}
})
});
result.then(value => {
const subs = {
"#{DNS_ADDRESSES}": value,
"#{SECRET_ID}": directory.directorySecret.secretArn
};
Object.entries(subs).forEach(
([key, value]) => {
domainJoinScript = domainJoinScript.replace(key, String(value));
}
);
new Stack(app, 'instances', networking.vpc, domainJoinScript);
}).catch(error => {
print(error)
});
Now this works, but it's far from a clean solution. My new Stack has multiple resources within it which means I have to pass the result of the SDK call through several levels, instead of just directly where it's needed, and if I had to make multiple SDK calls this would get even messier.
The core problem is the AWS SDK being purely asynchronous in JS, which means I have to use the fairly verbose pattern above to wrap it.
Does anyone have a better way to do this?

You could implement the AWS SDK calls using a AwsCustomResource
This allows calling AWS SDK functions on different Resource actions (oncreate, ondelete, onupdate)
The result data can be consumed by calling getData. This does further allow the cdk created CloudFormation Template to work without a client to run it.
One Example where I used this is the following:
const userPoolDomainDescription = new customResources.AwsCustomResource(this, 'user-pool-domain-description', {
onCreate: {
physicalResourceId: 'user-pool-domain-description',
service: 'CognitoIdentityServiceProvider',
action: 'describeUserPoolDomain',
parameters: {
Domain: userPoolDomain.domain
}
}
});
const dnsName = userPoolDomainDescription.getData('DomainDescription.CloudFrontDistribution').toString();

Related

What's the advantage of fastify-plugin over a normal function call?

This answer to a similar question does a great job at explaining how fastify-plugin works and what it does. After reading the explanation, I still have a question remaining; how is this different from a normal function call instead of using the .register() method?
To clarify with an example, how are the two approaches below different from each other:
const app = fastify();
// Register a fastify-plugin that decorates app
const myPlugin = fp((app: FastifyInstance) => {
app.decorate('example', 10);
});
app.register(myPlugin);
// Just decorate the app directly
const decorateApp = (app: FastifyInstance) => {
app.decorate('example', 10);
};
decorateApp(app);
By writing a decorateApp function you are creating your own "API" to load your application.
That said, the first burden you will face soon is sync or async:
decorateApp is a sync function
decorateAppAsync within an async function
For example, you need to preload something from the database before you can start your application.
const decorateApp = (app) => {
app.register(require('#fastify/mongodb'))
};
const businessLogic = async (app) => {
const data = await app.mongo.db.collection('data').find({}).toArray()
}
decorateApp(app)
businessLogic(app) // whoops: it is async
In this example you need to change a lot of code:
the decorateApp function must be async
the mongodb registration must be awaited
the main code that loads the application must be async
Instead, by using the fastify's approach, you need to update only the plugin that loads the database:
const applicationConfigPlugin = fp(
+ async function (fastify) {
- function (fastify, opts, next) {
- app.register(require('#fastify/mongodb'))
- next()
+ await app.register(require('#fastify/mongodb'))
}
)
PS: note that fastify-plugin example code misses the next callback since it is a sync function.
The next bad pattern will be high hidden coupling between functions.
Every application needs a config. Usually, the fastify instance is decorated with it.
So, you will have something like:
decorateAppWithConfig(app);
decorateAppWithSomethingElse(app);
Now, decorateAppWithSomethingElse will need to know that it is loaded after decorateAppWithConfig.
Instead, by using the fastify-plugin, you can write:
const applicationConfigPlugin = fp(
async function (fastify) {
fastify.decorate('config', 42);
},
{
name: 'my-app-config',
}
)
const applicationBusinessLogic = fp(
async function (fastify) {
// ...
},
{
name: 'my-app-business-logic',
dependencies: ['my-app-config']
}
)
// note that the WRONG order of the plugins
app.register(applicationBusinessLogic);
app.register(applicationConfigPlugin);
Now, you will get a nice error, instead of a Cannot read properties of undefined when the config decorator is missing:
AssertionError [ERR_ASSERTION]: The dependency 'my-app-config' of plugin 'my-app-business-logic' is not registered
So, basically writing a series of functions that use/decorate the fastify instance is doable but it adds
a new convention to your code that will have to manage the loading of the plugins.
This job is already implemented by fastify and the fastify-plugin adds many validation checks to it.
So, by considering the question's example: there is no difference, but using that approach to a bigger application
will lead to a more complex code:
sync/async loading functions
poor error messages
hidden dependencies instead of explicit ones

Mocha.js - How to save a global variable?

I'm working with Mocha.js for testing in a Node.js - Express.js - Firebase
I need a token from Firebase to access the API endpoints, I have a before hook in all my files, but after about 250 tests, probably calling the authentication endpoint multiple times, I'm getting rate limited by firebase.
I want to get the token once and use it in all my tests.
The tests are spread in different files, I have an index.js that requires them all.
I'm aware of Root Level Hooks, but how can I save the token and use it in all my separate files?
Thanks!
you can create a function that gets the token. then call it. then create your test suite only after that
function getToken(callback) {
//
}
// define tests
function allTests(token) {
describe(xxxxxx, function () {
it(xxxxxxxxx, function() {
//
})
});
}
// start all
getToken(function(token) {
allTests(token);
});
I managed to solve it myself, if anyone needs an answer on how to approach it, take a look at this.
I have multiple files where we write our unit testing, we unite them in an index.spec.js that we execute for testing ($ mocha index.spec.js)
I created a utility file that looks like this:
let token;
(() => { token = getYourToken() })()
module.exports = {
getToken: () => {
return new Promise((resolve) => {
const interval = setInterval(() => {
if (token) {
clearInterval(interval);
resolve(token);
}
}, 100);
});
}
};
Basically, it's a singleton, in the index.spec.js I require this file, executing the 'getYourToken()' once (add your logic to get token here). Then I store it in a variable that then I export.
In the export, I use an interval because my current code is not using promises, use your best practice method, interval + Promise worked for me.
This way I require this file in my tests and get the token I got at the beginning once, avoiding rate-limiting and any issue with firebase.
Create a JSON file in your test root directory.
Import the file.
Append a token property with the token value.
Then import it anywhere to access the token property .

Electron - How to share a single promise resolution from Main to multiple Renderers?

I'm new to Electron and trying to figure out the best way to handle a shared object.
Essentially, I want to initialize something once in the Main process, then use it in multiple renderer processes, like so:
// main.js
const node = rosnodejs.initNode(); // returns a promise
// renderer1.js
node.then((nh) => {
nh.subscribe("topic1");
})
// renderer2.js
node.then((nh) => {
nh.subscribe("topic2");
})
I was able to share node using remote, but then nh.subscribe becomes an anonymous function in my renderers and fails. Here's what I'm doing:
// main.js
global.node = rosnodejs.initNode(); // returns a promise
global.node.then((nh) => {
nh.subscribe("topic1"); // WORKS PERFECTLY!
})
// renderer1.js
const remote = require('electron').remote;
const node = remote.getGlobal('node');
node.then((nh) => {
nh.subscribe("topic2"); // FAILS MISERABLY.
})
Failure message is Error: Could not call remote function 'subscribe'. Check that the function signature is correct. Underlying error: type.datatype is not a function.
Is there a decent way to handle this? Should I be using ipcMain/ipcRenderer instead?

Electron app performance: blocking thread with ipcRenderer.sendSync

I have a music player built with Electron.
I am having some performance / process blocking that I didn't expect. I am trying to do some background processes for heavy IO operations. (determining songs duration and album covers)
I am doing this by calling methods through the electron.remote module.
I have noticed that the app didn't do these things asynchronously somehow.
I have been running the performance tool to check and saw the click handler taking a huge time.
Digging deeper I found that ipcRenderer.sendSync is called.
There is a warning about sendSync blocking nature in Electron Docs. But, my own code does not call it. So I suspect the remote module or something else in my code causing sendSync to be called.
The entire app code is on Github but here is an example of electron.remote usage.
The gist is something like this:
import {remote} from 'electron'
const fs = remote.require('fs')
const mm = remote.require('musicmetadata')
// read song file, IO
function readMetadata (filePath) {
return new Promise(function (resolve, reject) {
const stream = fs.createReadStream(filePath)
mm(stream, {duration: true}, function (err, metadata) {
// ...
})
})
}
// get metadata for an array of songs
async function refreshSongsDuration (songs) {
const songsMetadata = await Promise.all(songs.map((song) => readMetadata(song.filePath)))
return songs.map((song, index) => {
song.duration = songsMetadata[index].duration
return song
})
}
Then in a click handler I'll have something like this:
playArtist (artistID) {
const songs = this.library.getArtistSongs(artistID)
this.playlist.setSongs(songs)
musicPlayer.play()
const shouldGetDuration = songs.some((song) => song.duration === 0)
// This is expected to be asynchronous and non blocking.
if (shouldGetDuration) {
mediaLibrary.refreshSongsDuration(songs)
.then((updatedSongs) => {
this.playlist.set('songs', updatedSongs)
})
}
}
So, I guess the simple question here is, what am I doing wrong causing these blocking processes?
https://github.com/electron/electron/blob/master/docs/api/remote.md#remote-objects
Each object (including functions) returned by the remote module represents an object in the main process (we call it a remote object or remote function). When you invoke methods of a remote object, call a remote function, or create a new object with the remote constructor (function), you are actually sending synchronous inter-process messages.
every remote module is sync in nature.

Return value from callback function in service to component

I'm using the Cordova WifiWizard Plugin in my Ionic 2 mobile app. Everything works fine, when I run it in the *.ts file of my page. In this case my code looks like:
Declaring WifiWizard before #Component:
declare var WifiWizard:any;
Call of WifiWizard and success / error methods:
setCurrentWifiAsHomeWifi() {
WifiWizard.getCurrentSSID(this.ssidSuccess, this.ssidError);
}
ssidSuccess = (ssid) => {
this.userService.setHomeWifi(this.user.$key, ssid);
};
ssidError = (err) => {
console.log("WiFi Wizard Error"+err);
};
However, I don't want to put all this wifi-logic in the page-component, but move it to a wifi provider / service instead. But I don't get this service to return the ssid-string to the component.
EDIT:
I tried many different things and I carefully read the post, linked by gyre (thank you for that). However, I still can't figure out a solution for my problem, and I think it's slightly different to the linked question.
Let me sketch the structure of my problem to make it more obviously to you:
I'm calling the wifiService from homePage.ts, e.g. using a promise:
setCurrentWifiAsHomeWifi() {
this.wifiService.loadWifiData().then( result=> {
console.log("returned SSID: "+result);
});
In wifiService.ts I need to implement "loadWifiData" which calls the wifiWizard plugin like this:
WifiWizard.getCurrentSSID(this.getWifiSuccess, this.getWifiError);
If this is successful, it passes the SSID-string to the "getWifiSuccess" method, if it fails it passes the error to "getWifiError". However the plugin call doesn't return any value to my homePage.
How can I return the outcome (SSID or error) to my homePage? I'm stuck here. Appreciate your help! Hope it became clearer now.
I solved it by letting loadWifiData() return a Promise and defining the code for the success- and reject-function directly in this Promise.
loadWifiData() {
return new Promise( (resolve, reject) => {
WifiWizard.getCurrentSSID(
// if successful:
(ssid => {
resolve (ssid);
})
,
// if error:
(error => {
reject (error);
})
)
});
}

Categories

Resources