Is there a way to update headers of Chime Messaging API of aws-sdk/clients/chimesdkmessaging in ReactJs? - reactjs

I am using aws-sdk/clients/chimesdkmessaging to make requests to get/delete/send channel messages.
But the issue is that the temporary credentials are expired after 1 hour and I cannot use the services without renewing the credentials. I am able to renew them, but couldn't set them in the API Request header.
I tried to set the request header as :
request.httpRequest.headers["x-amz-security-token"] = NEW_TOKEN
But it didn't work. Also, I couldn't find anything on adding a middleware to these API's.
Here's my code
const ChimeMessaging = require("aws-sdk/clients/chimesdkmessaging");
const chimeMessaging = new ChimeMessaging();
async function sendChannelMessage(
channelArn,
messageContent,
persistence,
type,
member,
options = null
) {
const chimeBearerArn = createMemberArn(member.userId);
const params = {
ChimeBearer: chimeBearerArn,
ChannelArn: channelArn,
Content: messageContent,
Persistence: persistence, // Allowed types are PERSISTENT and NON_PERSISTENT
Type: type,
};
const request = (await chimeMessagingClient()).sendChannelMessage(params)
request.on("complete", function (err, data) {
request.httpRequest.headers["x-amz-security-token"] = NEW_TOKEN
});
const response = await request.promise();
const sentMessage = {
response: response,
CreatedTimestamp: new Date(),
Sender: { Arn: createMemberArn(member.userId), Name: member.username },
};
return sentMessage;
}

Related

Graphql: which is better to use variables or headers

I'm writing back on nestjs/mongodb and front on reactjs. And use graphql between them. I had some needed info in headers, or I passed it through variables in query.
Is it cheaper to pass it through variables or through context?
When user logs in, I'm setting headers: filialIds
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem(${localStorageAppPrefix}.token`);
return {
headers: {
...headers,
filialIds: `${localStorage.getItem(`${localStorageAppPrefix}.filialIds`) ?? ''}`,
authorization: token ? `Bearer ${token}` : '',
},
};
});
export const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});`
When user query smth, I'm checking his filialIds and role in Guard
`
#Injectable() export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = GqlExecutionContext.create(context);
const requiredRoles = this.reflector.getAllAndOverride<UserRoles[]>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredRoles) {
return true;
}
const queryFilialIds =
safeJSONParse(ctx.getContext()?.req?.headers?.filialids ?? '') ?? [];
const { roles, filialIds } = ctx.getContext()?.req?.user ?? {};
const hasRequiredFilials = filialIds?.every(
(filialId) => queryFilialIds.indexOf(filialId) !== -1,
);
const hasRequiredRoles = requiredRoles.some(
(role) => roles?.indexOf(role) !== -1,
);
return hasRequiredRoles || hasRequiredFilials;
}
}`
But I also need the access to filialIds and role in service, like here:
async getCount(context): Promise<number> {
const filialIds =
JSON.parse(context?.req?.headers?.filialids ?? '') ?? [];
return this.userModel.countDocuments({ filialIds: { $in: filialIds } });
}
So the question is: Should I use context or pass it from graphql query like here:
const { data } = useQuery(GET_USER, {
variables: { filialIds: filialIds ?? [] },
skip: !filialIds?.length,
});
There are many ways to solve this problem. I presume that filialIds is some sort of affiliation id?
If the affiliation id is an attribute of the user then you could include it in your JWT token or just append it to the user object whenever you check the JWT. Furthermore if it's a user attribute then there's no need to send it from the client to server at all.
If the affiliation id is an attribute of the site in a multi-tenant situation then a header makes perfect sense - not however that one site could potentially spoof another
If most or many of your resolvers need this variable then having it as a query variable seems tedious.

Is there a way to update headers of Chime Messaging API of aws-sdk/clients/chimesdkmessaging in ReactJs?

I am using aws-sdk/clients/chimesdkmessaging to make requests to get/delete/send channel messages.
But the issue is that the temporary credentials are expired after 1 hour and I cannot use the services without renewing the credentials. I am able to renew them, but couldn't set them in the API Request header.
I tried to set the request header as :
request.httpRequest.headers["x-amz-security-token"] = NEW_TOKEN
But it didn't work. Also, I couldn't find anything on adding a middleware to these API's.
Here's my code
const ChimeMessaging = require("aws-sdk/clients/chimesdkmessaging");
const chimeMessaging = new ChimeMessaging();
async function sendChannelMessage(
channelArn,
messageContent,
persistence,
type,
member,
options = null
) {
const chimeBearerArn = createMemberArn(member.userId);
const params = {
ChimeBearer: chimeBearerArn,
ChannelArn: channelArn,
Content: messageContent,
Persistence: persistence, // Allowed types are PERSISTENT and NON_PERSISTENT
Type: type,
};
const request = (await chimeMessagingClient()).sendChannelMessage(params)
request.on("complete", function (err, data) {
request.httpRequest.headers["x-amz-security-token"] = NEW_TOKEN
});
const response = await request.promise();
const sentMessage = {
response: response,
CreatedTimestamp: new Date(),
Sender: { Arn: createMemberArn(member.userId), Name: member.username },
};
return sentMessage;
}

Using a client-side only, readable store `set`, to refresh API access token

I am experimenting with using a readable store as the auth token for a web app.
The set function would fetch an authentication token from an API, and then set a timeout for X seconds to refresh the token before it expires. This would then continuously run every X seconds to set the token.
// auth.ts
export const ssr = false;
type LoginResponse = {
access_token: string;
expires: string;
}
export const authToken: Readable<string> = readable("", (set) => {
const setAuth = async () => {
const auth: LoginResponse = await authenticate();
set(auth.access_token);
authTimer(auth);
};
const authTimer = async (auth: LoginResponse) => {
const timeRemaining = Math.round(((new Date(auth.expires).getTime() - Date.now())) * 0.9);
setTimeout(async () => {
if (auth.access_token === authToken) {
console.log("refreshing auth token");
setAuth();
}
}, timeRemaining);
}
setTimeout(setAuth, 0)
});
The main issue I'm having is that this set function is called from the backend, causing an exception to raise when the authenticate() call fails. It should only be called once the front-end is loaded, as there needs to be client side headers set for the request to succeed.
Is this a generally good approach or is this not what set is intended for?
Also I seem to be unable to make value comparisons with the value of the readable (e.g. auth.access_token === authToken will always return false).
Finally, how can I queue API calls to only execute when the authToken is not an empty string?

How to send properly a message in twilio conversation with react?

I have a nextjs project and I want to replace the twilio programmable chat with twilio conversations.
I did the following steps:
I did one API which creates or gets (if it is already created) the conversation and returns to the client the conversation unique name and the token
Once I have the conversation unique name and the token I want to send client side one message.
To do so I did the following function:
import { Client, State } from '#twilio/conversations';
import toast from 'react-hot-toast';
const sendMessageToConversation = async (
token: string,
room: string,
message: string
) => {
const client = new Client(token);
client.on('stateChanged', async (state: State) => {
if (state === 'initialized') {
try {
const conversation = await client.getConversationByUniqueName(room);
await conversation.join();
if (message && String(message).trim()) {
await conversation.sendMessage(message);
}
} catch {
toast.error('Unable to create conversation, please reload this page');
}
}
});
};
the problem seems to be const conversation = await client.getConversationByUniqueName(room); which gives the following error:
What do you think I did wrong?
Also is it a better idea to build an API to send messages in this way? I would avoid this because of the possible overhead of the server
UPDATE
I tried to send a message through API. It works and it returns what I expect. For more detail I will put also the code I have on backend side which generates the tokens and the conversations.
I generate tokens for client side with:
import Twilio from 'twilio';
import { config } from '../config';
const client = require('twilio')(
config.TWILIO_ACCOUNT_SID,
config.TIWLIO_AUTH_TOKEN
);
const AccessToken = Twilio.jwt.AccessToken;
const ChatGrant = AccessToken.ChatGrant;
const SyncGrant = AccessToken.SyncGrant;
export const tokenGenerator = (identity: string) => {
const token = new AccessToken(
config.TWILIO_ACCOUNT_SID,
config.TWILIO_API_KEY,
config.TWILIO_API_SECRET
);
token.identity = identity || 'unknown';
if (config.TWILIO_CHAT_SERVICE_SID) {
const chatGrant = new ChatGrant({
serviceSid: config.TWILIO_CHAT_SERVICE_SID,
pushCredentialSid: config.TWILIO_FCM_CREDENTIAL_SID,
});
token.addGrant(chatGrant);
}
if (config.TWILIO_SYNC_SERVICE_SID) {
const syncGrant = new SyncGrant({
serviceSid: config.TWILIO_SYNC_SERVICE_SID || 'default',
});
token.addGrant(syncGrant);
}
return {
identity: token.identity,
token: token.toJwt(),
};
};
I create conversations with:
const client = require('twilio')(
config.TWILIO_ACCOUNT_SID,
config.TIWLIO_AUTH_TOKEN
);
export const createTwilioConversation = async (
partecipantsProfiles: Partial<User>[],
identity: string
) => {
const friendlyName: string = partecipantsProfiles
.map((el) => `${el.first_name} ${el.last_name}`)
.join(' - ');
const conversation = (await client.conversations.conversations.create({
friendlyName,
uniqueName: uuidv4(),
})) as TwilioConversationResponse;
await client.conversations
.conversations(conversation.sid)
.participants.create({ identity });
return conversation;
};
The flow I do in order to send messages is:
If I want to send a message I create a conversation by calling an API, executes the functions above and returns the room unique name and the token. I also store into my DB the room unique name, participants and other infos. I do this only for the first message. If a user has already chatted with another user, then I don't create a conversation anymore, but I return the unique name stored + token generated and I get/send the message client side
I have also tried to send a message through API and it works. I can't figure out why I still can't get the conversation by unique name client side. Maybe I should generate the token differently?
This is the method that sends messages server side:
export const sendMessage = async (
conversationSid: string,
author: string,
body: string
) => {
return await client.conversations
.conversations(conversationSid)
.messages.create({ author, body });
};
Your issue is that you are not using the default Conversations service, but you are not scoping your API calls on the server side to the service you need.
So in pages/api/utils/conversations you need to add .services(config.TWILIO_CHAT_SERVICE_SID) into all your API calls. See below:
export const createTwilioConversation = async (
chatTo: string,
myIdentity: string
) => {
const uniqueName = uuidv4();
const conversation = (await client.conversations
.services(config.TWILIO_CHAT_SERVICE_SID)
.conversations.create({
friendlyName: `Chat created by ${myIdentity}`,
uniqueName,
})) as TwilioConversationResponse;
await client.conversations
.services(config.TWILIO_CHAT_SERVICE_SID)
.conversations(conversation.sid)
.participants.create({ identity: chatTo });
await client.conversations
.services(config.TWILIO_CHAT_SERVICE_SID)
.conversations(conversation.sid)
.participants.create({ identity: myIdentity });
return conversation;
};
export const sendMessage = async (
conversationSid: string,
author: string,
body: string
) => {
return await client.conversations
.services(config.TWILIO_CHAT_SERVICE_SID)
.conversations(conversationSid)
.messages.create({ author, body });
};
Once you've done that, you need to one other thing. Because you add your participant to the conversation using the REST API, you don't need to join the conversation in the front-end. So you can remove the line
await conversation.join();
from src/twilio/index.ts.
One last thing, you can get better error messages in the front-end if you log out error.body rather than just error or error.message.
try {
const conversation = await client.getConversationByUniqueName(room);
if (message && String(message).trim()) {
await conversation.sendMessage(message);
}
} catch (error) {
console.log("error", error);
console.log("error body", error.body);
toast.error("Unable to create conversation, please reload this page");
}
Twilio have an official blog for Build a Chat App with Twilio Programmable Chat and React.js, please check it out once,
Here is the link - https://www.twilio.com/blog/build-a-chat-app-with-twilio-programmable-chat-and-react

I keep getting an error when using the clarifai 'Demographics" model

TypeError: Cannot read property 'language' of undefined
The demographics model was working few months ago when I used it as 'Clarifai.DEMOGRAPHICS_MODEL' but now it gives a 404 error for the same. I think now I am supposed to use it as 'Clarifai.Demographics' but it gives the error shown in picture.
Am I doing something wrong or is it some Clarifai issue? I am relatively new to react programming and just noticed the app that I had made before using 'Clarifai.DEMOGRAPHICS_MODEL' started showing errors suddenly.
For now, I am just trying to make this work
const onSubmit = () => {
app.models.predict(Clarifai.Demographics, "https://static.independent.co.uk/s3fs-public/thumbnails/image/2015/06/06/15/Chris-Pratt.jpg")
.then(res => console.log(res))
}
NEW EDIT :
Finally, now I can retrieve data from clarifai and send it to frontend. Keeping the backend code here for reference if anyone needs.
--------server.js---------
const express = require('express')
const cors = require('cors')
const posts = require('./demographics.js')
const app = express()
app.use(cors())
app.use(express.json())
const PORT = process.env.PORT || 5000
app.post('/data', posts.demographics)
app.listen(PORT, ()=> {
console.log('Working at port ',PORT)
})
--------demographics.js---------
const {ClarifaiStub, grpc} = require("clarifai-nodejs-grpc");
const stringify = require('json-stringify-safe');
const demographics = (req,res) => {
const stub = ClarifaiStub.grpc()
const metadata = new grpc.Metadata();
metadata.set("authorization", "Key {My_Key}");
stub.PostWorkflowResults(
{
workflow_id: "Demographics",
inputs: [
{data: {image: {url: req.body.input}}}
]
},
metadata,
(err, response) => {
if(response){
const data = stringify(response.results[0].outputs[4].data.regions, null, 2)
res.send(data)
}
else {
console.log(err)
res.status(400)
}
}
)
}
You have to call the request either from the front end or from the backend.
And the demographics now only support requests from the backend.
const {ClarifaiStub} = require("clarifai-nodejs-grpc");
const grpc = require("#grpc/grpc-js");
const metadata = new grpc.Metadata();
metadata.set("authorization", "{My key}");
const stub = ClarifaiStub.json()
stub.PostWorkflowResults(
{
workflow_id: "Demographics",
inputs: [
{data: {image: {url: "https://static.independent.co.uk/s3fs-public/thumbnails/image/2015/06/06/15/Chris-Pratt.jpg"}}}
]
},
metadata,
(err, response) => {
if(response){
console.log(response.results[0].outputs[2].data.regions[0].data.concepts
)
}else {
console.log(err)
}
use this for returning results
// response.results[0].outputs[2].data.regions[0].data.concepts, muliticulture,
// response.results[0].outputs[3].data.regions[0].data.concepts, gender,
//response.results[0].outputs[4].data.regions[0].data.concepts, age,
The Clarifai JS Rest client is now depreciated, the model IDs are not updated and this specific Demographics model is also on the course of depreciation. You can replace it with the new gRPC NodeJS API: https://docs.clarifai.com/api-guide/api-overview/api-clients
You can then get a demographics prediction by calling the new Demographics workflow: https://docs.clarifai.com/api-guide/workflows/workflow-predict (replace 'YOUR_WORKFLOW_ID' by 'Demographics')

Resources