Streaming video from AWS Kinesis to JS client - javascript

I'm trying to consume the video from an AWS Kinesis stream. The stream is visible in the AWS console, but I cannot consume it in the JS application I'm trying to create.
I've been following this tutorial, but cannot get the streaming URL.
My code is here:
import React, { Component} from 'react'
import ReactPlayer from 'react-player'
import AWS from "aws-sdk";
import { STREAM_NAME, ACCESS_KEY_ID, SECRET_ACCESS_KEY, REGION } from '../secrets'
var streamName = STREAM_NAME;
// Step 1: Configure SDK Clients
var options = {
accessKeyId: ACCESS_KEY_ID,
secretAccessKey: SECRET_ACCESS_KEY,
region: REGION
}
var kinesisVideo = new AWS.KinesisVideo(options);
var kinesisVideoArchivedContent = new AWS.KinesisVideoArchivedMedia(options);
// Step 2: Get a data endpoint for the stream
kinesisVideo.getDataEndpoint({
StreamName: streamName,
APIName: "GET_HLS_STREAMING_SESSION_URL"
}, function(err, response) {
if (err) { return console.error(err); }
console.log('Data endpoint: ' + response.DataEndpoint);
kinesisVideoArchivedContent.endpoint = new AWS.Endpoint(response.DataEndpoint);
});
// Step 3: Get an HLS Streaming Session URL
console.log('Fetching HLS Streaming Session URL');
var playbackMode = 'LIVE'; // 'LIVE' or 'ON_DEMAND'
//var startTimestamp = new Date('START_TIMESTAMP'); // For ON_DEMAND only
//var endTimestamp = new Date('END_TIMESTAMP'); // For ON_DEMAND only
var fragmentSelectorType = 'SERVER_TIMESTAMP'; // 'SERVER_TIMESTAMP' or 'PRODUCER_TIMESTAMP'
const SESSION_EXPIRATION_SECONDS = 60*60
console.log(kinesisVideo)
const hlsUrl = kinesisVideoArchivedContent.getHLSStreamingSessionURL({
StreamName: streamName,
//StreamARN: "arn:aws:kinesisvideo:us-east-1:635420739373:stream/mr-pinchers-dot-org/1561848963391",
PlaybackMode: playbackMode,
HLSFragmentSelector: {
FragmentSelectorType: fragmentSelectorType,
TimestampRange: playbackMode === 'LIVE' ? undefined : {
// StartTimestamp: startTimestamp,
// EndTimestamp: endTimestamp
}
},
Expires: parseInt(SESSION_EXPIRATION_SECONDS)
}, function(err, response) {
if (err) { return console.error("Darn", err); }
console.log('HLS Streaming Session URL: ' + response.HLSStreamingSessionURL, response);
}
)
console.log("here", hlsUrl)
class Home extends Component {
render () {
return <ReactPlayer url={hlsUrl} playing={true} />
}
}
export default Home
The response I'm getting in Step 3 (response.HLSStreamingSessionURL) is undefined.
Step 2 runs fine, and I get an endpoint back, so I'm confident that it's not a permissions problem.
Part of me thinks that I should be using some async/await calls but I'm not sure, still pretty new to JS and all that async stuff so didn't know how to incorporate it into this.
I've spent quite a bit of time trying to figure this out but the documentation on Kinesis is still pretty light, although if someone has a good resource for it, please let me know.

This is basic JavaScript async behavior. You're executing step 3 before step 2 is complete. You can't use the response before it's happened.
You can fix this by starting step 3 when step 2 has completed, as follows:
kinesisVideo.getDataEndpoint({
StreamName: streamName,
APIName: "GET_HLS_STREAMING_SESSION_URL"
}, function(err, response) {
if (err) { return console.error(err); }
console.log('Data endpoint: ' + response.DataEndpoint);
kinesisVideoArchivedContent.endpoint = new AWS.Endpoint(response.DataEndpoint);
var playbackMode = 'LIVE';
var fragmentSelectorType = 'SERVER_TIMESTAMP';
const SESSION_EXPIRATION_SECONDS = 60*60
kinesisVideoArchivedContent.getHLSStreamingSessionURL({...});
// remainder of code here
});
Or you can use async/await and promise variants of the AWS SDK methods like so:
(async () => {
const kv_response = await kv.getDataEndpoint({...}).promise();
// ...
const hls_response = await kvac.getHLSStreamingSessionURL({...}).promise();
})();
Note that await may only be used inside an async function, hence the anonymous async wrapper.

Related

Looking for a solution to stream dynamic changes in JSON data on to browser without refreshing

I have a node.js / next.js api built that essentially does a bunch of stuff after the user submits text into a form on the front end. One of the things it does is write stage completion messages periodically to a JSON file to signify the completion of certain stages.
my api looks something like this
import dbConnect from '../../../lib/dbConnect'
import Demo from '../../../models/Demo'
import fs from 'fs'
import shell from 'shelljs';
export default async function handler(req, res) {
const {
method,
body,
} = req
await dbConnect()
switch (method) {
case 'GET':
try {
const demos = await Demo.find({})
res.status(200).json({ success: true, data: demos })
} catch (error) {
res.status(400).json({ success: false })
}
break
case 'POST':
try {
const initialjson = '[]'
const timestamp = Date.now();
// stage 1
if (shell.exec('./initial_checks.sh').code !== 0) {
shell.echo('Sorry stage failed');
shell.exit(1);
};
const objSuccess1 = JSON.parse(initialjson);
objSuccess1.push("Stage 1 complete", + timestamp);
const finalJSONSuccess1 = JSON.stringify(objSuccess1);
fs.writeFileSync('success-stage.json', finalJSONSuccess1);
// stage 2
if (shell.exec('./secondary_checks.sh').code !== 0) {
shell.echo('Sorry stage failed');
shell.exit(1);
};
const objSuccess2 = JSON.parse(initialjson);
objSuccess2.push("Stage 2 complete", + timestamp);
const finalJSONSuccess2 = JSON.stringify(objSuccess2);
fs.writeFileSync('success-stage.json', finalJSONSuccess2);
const demo = await Demo.create(
req.body
)
res.status(201).json({ success: true, data: demo })
} catch (error) {
res.status(400).json({ success: false })
}
break
default:
res.status(400).json({ success: false })
break
}
}
I am using socket.io, my server.js file is
server.js
const app = require("express")();
const server = require("http").Server(app);
const io = require("socket.io")(server);
const next = require("next");
const dev = process.env.NODE_ENV !== "production";
const nextApp = next({ dev });
const nextHandler = nextApp.getRequestHandler();
let port = 3000;
const fs = require('fs')
const data = fs.readFileSync('success-stage.json', 'utf8')
io.on("connect", (socket) => {
socket.emit("now", {
message: data
});
});
nextApp.prepare().then(() => {
app.all("*", (req, res) => {
return nextHandler(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
console.log("> Ready on port: " + port);
});
});
and here is the pages/index.js file
import { useEffect, useRef, useState } from "react";
import io from "socket.io-client";
export default function IndexPage() {
const socket = useRef();
const [hello, setHello] = useState();
useEffect(() => {
socket.current = io();
socket.current.on("now", (data) => {
setHello(data.message);
});
}, []);
return <h1>{hello}</h1>;
}
so at this point we are seeing the 2nd message from my JSON file match what is rendered on the frontend when I build my application. It looks like this
["Stage 2 complete",1664289144513]
I am wondering how I can stream this data onto the front end for clients without having to refresh the page? I need it to show the current stage's success message... There are 5 total stages, so i guess i am looking for a way to either stream data or maybe to revalidate the browser window like every second without having to refresh... is this possible?
Any help would be greatly appreciated... Thanks in advance for your time everyone...
You've already got a solution implemented that can handle this. What you're describing is exactly what sockets are for -- bidirectional communication between the client and server without refreshing the page.
Just create a new socket listener on the frontend for a new topic, maybe "stageStatus", and then emit messages to that topic on the backend at various stages in the process. That's it!

Cloudinary Signed Uploads with Widget

Documentation is extremely frustrating.
I'm using the upload widget to try to allow users to upload multiple pictures for their profile. I can't use unsigned uploads because of the potential for abuse.
I would much rather upload the file through the upload widget instead of through the server as it seems like it should be so simple
I've pieced together what I think should work but it is still saying: Upload preset must be whitelisted for unsigned uploads
Server:
// grab a current UNIX timestamp
const millisecondsToSeconds = 1000;
const timestamp = Math.round(Date.now() / millisecondsToSeconds);
// generate the signature using the current timestmap and any other desired Cloudinary params
const signature = cloudinaryV2.utils.api_sign_request({ timestamp }, CLOUDINARY_SECRET_KEY);
// craft a signature payload to send to the client (timestamp and signature required)
return signature;
also tried
return {
signature,
timestamp,
};
also tried
const signature = cloudinaryV2.utils.api_sign_request(
data.params_to_sign,
CLOUDINARY_SECRET_KEY,
);
Client:
const generateSignature = async (callback: Function, params_to_sign: object): Promise<void> => {
try {
const signature = await generateSignatureCF({ slug: 'xxxx' });
// also tried { slug: 'xxxx', params_to_sign }
callback(signature);
} catch (err) {
console.log(err);
}
};
cloudinary.openUploadWidget(
{
cloudName: 'xxx',
uploadPreset: 'xxxx',
sources: ['local', 'url', 'facebook', 'dropbox', 'google_photos'],
folder: 'xxxx',
apiKey: ENV.CLOUDINARY_PUBLIC_KEY,
uploadSignature: generateSignature,
},
function(error, result) {
console.log(error);
},
);
Let's all take a moment to point out how horrible Cloudinary's documentation is. It's easily the worst i've ever seen. Nightmare fuel.
Now that i've got that off my chest... I really needed to be able to do this and I spent way too long banging my head against walls for what should be extremely simple. Here it is...
Server (Node.js)
You'll need an endpoint that returns a signature-timestamp pair to the frontend:
import cloudinary from 'cloudinary'
export async function createImageUpload() {
const timestamp = new Date().getTime()
const signature = await cloudinary.utils.api_sign_request(
{
timestamp,
},
process.env.CLOUDINARY_SECRET
)
return { timestamp, signature }
}
Client (Browser)
The client makes a request to the server for a signature-timestamp pair and then uses that to upload a file. The file used in the example should come from an <input type='file'/> change event etc.
const CLOUD_NAME = process.env.CLOUDINARY_CLOUD_NAME
const API_KEY = process.env.CLOUDINARY_API_KEY
async function uploadImage(file) {
const { signature, timestamp } = await api.post('/image-upload')
const form = new FormData()
form.append('file', file)
const res = await fetch(
`https://api.cloudinary.com/v1_1/${CLOUD_NAME}/image/upload?api_key=${API_KEY}&timestamp=${timestamp}&signature=${signature}`,
{
method: 'POST',
body: form,
}
)
const data = await res.json()
return data.secure_url
}
That's it. That's all it takes. If only Cloudinary had this in their docs.
Man. I hate my life. I finally figured it out. It literally took me beautifying the upload widget js to understand that the return of the function should be a string instead of an object even though the docs make it seem otherwise.
Here is how to implement a signed upload with a Firebase Cloud Function
import * as functions from 'firebase-functions';
import cloudinary from 'cloudinary';
const CLOUDINARY_SECRET_KEY = functions.config().cloudinary.key;
const cloudinaryV2 = cloudinary.v2;
module.exports.main = functions.https.onCall(async (data, context: CallableContext) => {
// Checking that the user is authenticated.
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.',
);
}
try {
return cloudinaryV2.utils.api_sign_request(data.params_to_sign, CLOUDINARY_SECRET_KEY);
} catch (error) {
throw new functions.https.HttpsError('failed-precondition', error.message);
}
});
// CLIENT
const uploadWidget = () => {
const generateSignature = async (callback: Function, params_to_sign: object): Promise<void> => {
try {
const signature = await generateImageUploadSignatureCF({ params_to_sign });
callback(signature.data);
} catch (err) {
console.log(err);
}
};
cloudinary.openUploadWidget(
{
cloudName: 'xxxxxx',
uploadSignature: generateSignature,
apiKey: ENV.CLOUDINARY_PUBLIC_KEY,
},
function(error, result) {
console.log(error);
},
);
};

Finding an API for getting lottery numbers from EuroMillions

I have been trying to create a google assistant interaction for checking to see if the most recently drawn lottery results match the user's
I have tried an API created by 'CreativeSolutions' however the API does not seem to be responding to requests and I cannot find any other alternatives.
'use strict';
const { dialogflow } = require('actions-on-google');
const functions = require('firebase-functions');
const app = dialogflow({ debug:true });
app.intent('wednesdays number', (conv, {number}) => {
var lottoNumber = number;
if (lottoNumber == '01') {
return conv.ask('Thats correct, want to try again?');
}
var unirest = require("unirest");
var req = unirest("GET", "https://euromillions.p.rapidapi.com/ResultsService/FindLast");
req.headers({
"x-rapidapi-host": "euromillions.p.rapidapi.com",
"x-rapidapi-key": "6176bf0ce5mshda3ef945e5d809fp156f41jsnd333104ff8d2"
});
req.end(function (res) {
if (res.error) throw new Error(res.error);
console.log(res.body);
});
});
The EuroMillions does not have a free public-facing official API so this would be unable to be completed.
But you can use this API on RapidAPI: https://rapidapi.com/jribeiro19/api/euro-millions/
There is a free access to get results and paid access to get some more features.
disclaimer: I'm the developer/owner.

Node.js: Awaiting a Require

I'm new the Node.js and I've been working with a sample project by a third party provider and I'm trying to use Azure Key Vault to store configuration values.
I'm having trouble getting a process to wait before executing the rest. I'll try to detail as much as I know.
The sample project has a file named agent.js which is the start page/file. On line 16 (agent_config = require('./config/config.js')[process.env.LP_ACCOUNT][process.env.LP_USER]) it calls a config file with values. I'm trying to set these value using Key Vault. I've tried many combinations of calling functions, and even implementing async / await but the value for agent_config always contains a [Promise] object and not the data returned by Key Vault.
If I'm right, this is because the Key Vault itself uses async / await too and the config file returns before the Key Vault values are returned.
How can Key Vault be added/implemented in a situation like this?
Here's what I've tried:
First updated agent.js to
let agent_config = {};
try {
agent_config = require('./config/config.js')['123']['accountName'];
} catch (ex) {
log.warn(`[agent.js] Error loading config: ${ex}`)
}
console.log(agent_config);
Test 1
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
function getValue(secretName, secretVersion) {
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' }).then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
module.exports = {
'123': {
'accountName': {
accountId: getValue('mySecretName', '')
}
}
};
Results
{ accountsId: undefined }
Test 2
Made getValue an async function and wrapped it around another function (tried without the wrapping and didn't work either)
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
async function getValue(secretName, secretVersion) {
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' }).then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
async function config() {
module.exports = {
'123': {
'accountName': {
accountId: await getValue('mySecretName', '')
}
}
};
}
config();
Results
{}
Test 3
Made getValue an async function and wrapped it around another function (tried without the wrapping and didn't work either)
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
async function getValue(secretName, secretVersion) {
return msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
module.exports = {
'123': {
'accountName': {
accountId: getValue('mySecretName', '')
}
}
};
config();
Results
{ accountId: { <pending> } }
Other
I've tried many others ways like module.exports = async (value) =< {...} (found through other questions/solutions without success.
I'm starting to think I need to do some "waiting" on agent.js but I haven't found good info on this.
Any help would be great!
One issue is that your getValue function is not returning anything as your returns need to be explicit.
(and without the promise being returned, there's nothing to await on)
async function getValue(secretName, secretVersion) {
return msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
You could also get away with less explicit returns using arrow functions..
const getValue = async (secretName, secretVersion) =>
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then(credentials => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion)
.then(response => response.Value);
});
Introducing the Azure Key Vault read, which is async, means your whole config read is async. There' nothing you can do to get around that. This will mean that the code that uses the config will need to handle it appropriately. You start by exporting an async function that will return the config..
async function getConfig() {
return {
'123': {
'accountName': {
accountId: await getValue('mySecretName', '')
}
}
};
}
module.exports = getConfig;
In your agent code you call that function. This will mean that your agent code will need to be wrapped in a function too, so maybe something like this..
const Bot = require('./bot/bot.js');
const getConfig = require('./config/config.js');
getConfig().then(agentConfig => {
const agent = new Bot(agentConfig);
agent.on(Bot.const.CONNECTED, data => {
log.info(`[agent.js] CONNECTED ${JSON.stringify(data)}`);
});
});
The package azure-keyvault has been deprecated in favor of the new packages to deal with Keyvault keys, secrets and certificates separately. For your scenario, you can use the new #azure/keyvault-secrets package to talk to Key Vault and the new #azure/identity package to create the credential.
const { SecretClient } = require("#azure/keyvault-secrets");
const { DefaultAzureCredential } = require("#azure/identity");
async function getValue(secretName, secretVersion) {
const credential = new DefaultAzureCredential();
const client = new SecretClient(KEY_VAULT_URI, credential);
const secret = await client.getSecret(secretName);
return secret.value;
}
The DefaultAzureCredential assumes that you have set the below env variables
AZURE_TENANT_ID: The tenant ID in Azure Active Directory
AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant
AZURE_CLIENT_SECRET: The client secret for the registered application
To try other credentials, see the readme for #azure/identity
If you are moving from the older azure-keyvault package, checkout the migration guide to understand the major changes

By using ledger nano s, I wanna sign a transaction and send it

I'm trying to send ethereum transaction that sends ERC20 tokens to someone with Ledger Nano S through Node.JS but I'm not able to successfully sign and send this transaction.
First of all, I signed the transaction through the method, signTransaction, of ledgerhq API and then after signing it, I sended it to the main net by using sendSignedTransaction. When I execute below code, Ledger receives request and shows details of a transaction. However, after pressing Ledger's confirm button, the console returns error 'Returned error: Invalid signature: Crypto error (Invalid EC signature)'.
import AppEth from "#ledgerhq/hw-app-eth";
import TransportU2F from "#ledgerhq/hw-transport-u2f";
import TransportNodeHid from "#ledgerhq/hw-transport-node-hid";
import EthereumTx from "ethereumjs-tx"
const Web3 = require('web3');
import { addHexPrefix, bufferToHex, toBuffer } from 'ethereumjs-util';
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
var destAddresses = ['0xa6acFa18468786473269Dc1521fd4ff40F6481D9'];
var amount = 1000000000000;
var i=0;
var contract = new web3.eth.Contract([token contract ABI... ], '0x74a...');
const data1 = contract.methods.transfer(destAddresses[0], amount).encodeABI();
const exParams = {
gasLimit: 6e6,
gasPrice: 3e9,
from: '0x1A...',
data : data1,
to: '0x74a...',
value: '0x00',
nonce: "0x0",
chainId: 1,
v: "0x01",
r: "0x00",
s: "0x00"
}
async function makeSign(txParams) {
const tx = new EthereumTx(txParams);
const txHex = tx.serialize().toString("hex");
const signedTransaction = '0x' + txHex;
let transport;
try {
transport = await TransportNodeHid.create();
let eth2 = new AppEth(transport);
const result = await eth2.signTransaction("m/44'/60'/0'/0", txHex).then(result => {
web3.eth.sendSignedTransaction('0x' + txHex)
.then(res => {
console.log(res);
}).catch(err => {
console.log('sendSignedTransaction');
console.log(err);
});
}).catch(err => {
console.log('signTransaction');
console.log(err);
});
txParams.r = `0x${result.r, 'hex'}`;
txParams.s = `0x${result.s, 'hex'}`;
txParams.v = `0x${result.v, 'hex'}`;
return result;
} catch (e) {
console.log(e);
}
}
makeSign(exParams).then(function () {
console.log("Promise Resolved2");
}.catch(function () {
console.log("Promise Rejected2");
});
When I only use signTransaction function, I can confirm the transaction in the ledger device and return txhash on the console. However, ultimately I want to broadcast a transaction to the main net. Could you please give me any idea? I want any feedback. Also, if there are any examples of creating and broadcasting a raw transaction by using the ledger, notice me please.
Your code already sends the transaction to the network. However, just awaiting the "send" promise only gives you the transaction hash, not the receipt. You need to treat it as an event emitter and wait for the 'confirmation' event.
const serializedTx = tx.serialize();
web3.eth.sendSignedTransaction(serializedTx.toString('hex'))
.once('transactionHash', hash => console.log('Tx hash', hash))
.on('confirmation', (confNumber, receipt) => {
console.log(`Confirmation #${confNumber}`, receipt);
})
.on('error', console.error);
To send it to mainnet as you mention, you can either run a local geth node on port 8545 and use your code unchanged, or point web3 at infura or similar.

Categories

Resources