I have an issue where I can return data from an AXIOS request but I cannot save that data to the conversation user storage (conv.user.storage.caseNumber) using Actions on Google Node.js library. I've tried many different approaches but none are working. I can save data from the intent fine (ex. conv.user.storage.subject and conv.user.storage.description). I've verified via console.log() that the data (i.e. caseNumber) is being returned properly in the response. Any help would be greatly appreciated.
Here is the code:
// Index
const SalesForceProxy = require('./classes/SalesForceProxy');
let proxy = new SalesForceProxy();
app.intent('getDescription - yes - CreateConfirmation', proxy.createCase);
// Proxy.js
module.exports = function () {
this.createCase = function (conv) {
return new Promise(function( resolve, reject) {
axios.post('https://mysite.my.salesforce.com/services/oauth2/token',querystring.stringify(params)).then(function(response){
var caseData = {
"account" : conv.user.storage.accountId,
"recordType" : conv.user.storage.recordTypeId,
"priority" : conv.user.storage.priority,
"subject" : conv.user.storage.subject,
"description" : conv.user.storage.description
};
axios.post('https://mysite.my.salesforce.com/services/apexrest/voicetocase/create',caseData,
{
headers:
{
'Authorization': "Bearer " + response.data.access_token,
'Content-Type': 'application/json'
}
}
)
.then(function(response){
conv.user.storage.caseNumber = response.data.caseNumber;
}.bind({conv: conv}))
.catch((error) => {
console.log(error);
});
resolve()
}.bind({conv: conv})).catch((error) => {
console.log(error);
reject(err);
});
});
}
}
It looks like you're resolving the promise too soon, before you get to set the user storage, which is a second promise.
axiom.post(...)
.then() {
conv.user.storage.X = "";
resolve();
}
This should work as expected.
Related
I am looking for a generic way to replay a fetch if I get a 401 response.
My application is a SPA using OIDC. Our frontend developers utilise fetch, and a ServieWorker injects the access_token into the AJAX request before sending the request to the API(s). There are times when a fetch occurs but access_token is expired. When that happens, I want to use the refresh_token to get a new access_token and then replay the fetch, returning the replayed fetch in the Promise. Ideally, this would be something the frontend developers would not even know is happening.
Meaning that a UI developer will code something like what's below (remember, the access_token is injected via ServiceWorker):
fetch("https://backend.api/user/get/1")
.then(resp =>
{
console.log("user information is XYZ. Raw response:", resp);
})
When really what's happening in the background is:
[Initial request] > [Expired token response] > [Request new token] > [Initial request replayed]
I've experimented with overriding the fetch method with what's below, but I can't figure out a generic way to recreate/clone the original fetch:
window.fetch = new Proxy(window.fetch, {
apply(fetch, that, args) {
// Forward function call to the original fetch
const result = fetch.apply(that, args);
// Do whatever you want with the resulting Promise
result.then(resp =>
{
if(resp.status == 400 || resp.status == 401)
{
let rt = getRefreshToken();
return fetch("https://idaas.provider/get/new/token", {
"method": "POST",
"body": new URLSearchParams({
grant_type: "refresh_token",
refresh_token: rt,
client_id: client_id_str
})
});
}
}).then(resp =>
{
let new_token = resp.new_token;
send_new_token_to_service_worker(new_token);
return new_token
}).then(tok =>
{
// How do I replay the original request?
})
return result;
}
});
The goal is to simplify what the UI developers need to handle. I want them focused on UX and have this sort of error handling done in the background.
Note: If necessary, I would be open to not using fetch and instead utilising a wrapper method. Obviously, because of the code already written surrounding fetch, the new method would need to accept the same arguments and produce the same return.
window.fetch = new Proxy(window.fetch, {
apply(fetch, that, args) {
let fetchApi = [
fetch(args.shift())
];
let fetchToken = [
fetch("https://idaas.provider/get/new/token", {
method: "POST",
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: getRefreshToken(),
client_id: client_id_str,
})
}).then(token => send_new_token_to_service_worker(token)),
];
return Promise.allSettled(fetchApi).then(results => {
let rejected = results
.map(result => result.status)
.includes("rejected");
if (rejected) {
return Promise.all([...fetchToken, ...fetchApi]).then(results => results.at(1));
} else {
return results.at(0).value;
}
});
},
});
Usage fetch("https://your-api").then(resp => resp);
I'm trying to make an api call that allows me to edit a single user. The issue that I'm experiencing is that despite the call being successful (and no errors appearing), the changes are not saving. Can someone kindly guide me as to what I'm doing wrong exactly, please? I feel that I'm missing a function that allows me to save the changes after I make the call, but I'm not entirely sure how to go about this.
Edit user details:
setup() {
const store = vuexStore;
const adminId = router.currentRoute.params.adminId;
/** Edit **/
function editUser(formData) {
formData.adminId = adminId;
editAdminAccount(formData).then(response => {
if (response) {
redirectUserTo(ROUTE_NAMES_ADMIN.ADMIN_ACCOUNTS);
saveUserChanges(formData);
}
})
}
// Action
function editAdminAccount(data) {
return store.dispatch(UPDATE_ADMIN_ACCOUNT, data);
}
getSelectedAdmin(adminId);
const selectedAdmin = computed(() => store.getters.getSelectedAdmin);
function getSelectedAdmin(adminId) {
return store.dispatch(GET_ADMIN_BY_ID, adminId)
}
return {
editUser,
selectedAdmin,
}
}
Actions:
updateAdminAccount({commit}, payload) {
let formData = new FormData()
formData.append('email', payload.email)
formData.append('name', payload.name)
formData.append('password', payload.password);
return apiHandler.put(`user/admin/${payload.adminId}`, formData, apiHandler.getAuthHeader()).then(response => {
return !!apiHandler.isSuccess(response.status);
}).catch(error => {
commit(SET_API_ERROR, error);
});
},
You should maybe check what the api call is returning with some console.logs to be sure of the data that is sent back.
Nevertheless, do not have to work with formdata, you can send your query items directly :
updateAdminAccount({commit}, payload) {
return apiHandler.put(`user/admin/${payload.adminId}`, payload, apiHandler.getAuthHeader())
.then(response => !!apiHandler.isSuccess(response.status))
.catch(error => commit(SET_API_ERROR, error));
}
You also should edit the user directly after the api call in the action, and not from the template. So that the logic is kept at one place :
updateAdminAccount({commit}, payload) {
return apiHandler.put(`user/admin/${payload.adminId}`, payload, apiHandler.getAuthHeader())
.then(response => {
if (!!apiHandler.isSuccess(response.status)) {
commit('UPDATE_ADMIN', payload) // payload or response.data depending if api is returning edited object
}
return !!apiHandler.isSuccess(response.status)
})
.catch(error => commit(SET_API_ERROR, error));
}
React newbie here, but proficient in Django.I have a simple fetch function which worked perfectly but then my project had no login authentication involved. Now that I have configured the login system, my backend refuses to serve requests with any access tokens. My login authentication is very new to me and was more or less copied from somewhere. I am trying to understand it but am not able to. I just need to know how to convert my simple fetch function to include the getAccessToken along the request in it's headers so my backend serves that request.
Here is my previously working simple fetch function :
class all_orders extends Component {
state = {
todos: []
};
async componentDidMount() {
try {
const res = await fetch('http://127.0.0.1:8000/api/allorders/'); // fetching the data from api, before the page loaded
const todos = await res.json();
console.log(todos);
this.setState({
todos
});
} catch (e) {
console.log(e);
}
}
My new login JWT authentication system works perfectly, but my previous code is not working and I keep getting error
"detail": "Authentication credentials were not provided."
This is is the accesstoken I am not able to 'combine' with my preivous fetch function:
const getAccessToken = () => {
return new Promise(async (resolve, reject) => {
const data = reactLocalStorage.getObject(API_TOKENS);
if (!data)
return resolve('No User found');
let access_token = '';
const expires = new Date(data.expires * 1000);
const currentTime = new Date();
if (expires > currentTime) {
access_token = data.tokens.access;
} else {
try {
const new_token = await loadOpenUrl(REFRESH_ACCESS_TOKEN, {
method: 'post',
data: {
refresh: data.tokens.refresh,
}
});
access_token = new_token.access;
const expires = new_token.expires;
reactLocalStorage.setObject(API_TOKENS, {
tokens: {
...data.tokens,
access: access_token
},
expires: expires
});
} catch (e) {
try {
if (e.data.code === "token_not_valid")
signINAgainNotification();
else
errorGettingUserInfoNotification();
} catch (e) {
// pass
}
return reject('Error refreshing token', e);
}
}
return resolve(access_token);
});
};
If you're looking for a way how to pass headers in fetch request, it's pretty straight forward:
await fetch('http://127.0.0.1:8000/api/allorders/', {
headers: {
// your headers there as pair key-value, matching what your API is expecting, for example:
'details': getAccessToken()
}
})
Just don't forget to import your getAccessToken const, if that's put it another file, and I believe that would be it. Some reading on Fetch method
I would like to make an upvote function. I have some posts stored in database with Firebase. I would like to let people upvote (or downvote) for a post.
Here is in my ctrl :
upvote(advice) {
advice.votes = advice.votes + 1;
return this.httpService.updateData(advice)
.subscribe((res:Response) => {
console.log('in subscribe', res);
});
}
And in my service :
updateData (data:any): Observable<any> {
let datas = JSON.stringify(data);
const headers = { 'Authorization': 'secret' };
return this.http.put(this.url + 'data.json', datas, headers)
.catch((e) => {
console.log(e);
})
.finally(() => {
console.log('finally');
})
}
When I check the response in subscribe method inside the controller, I can see the updated value, but my database is now destroyed.
Before :
After :
As you can see, there no more real object after, it replaces everything.
I think I have to change the url or check the post id, but I don't see how to do.
I didn't find anything in the firebase documentation.. So if someone has an idea..
EDIT AFTER #Luke answer
I updated my code like this :
In my ctrl :
upvote(advice) {
advice.votes = advice.votes + 1;
return this.httpService.updateData(advice)
.subscribe((res:Response) => {
console.log(res);
return res;
})
}
And in the service :
getId() {
this.db.list('data').snapshotChanges().map(actions => {
return actions.map(action => ({ key: action.key, ...action.payload.val() }));
}).subscribe(items => {
return items.map(item => item.key);
});
}
updateData (data:any): Observable<any> {
let updateUrl = this.url + '-p1FFxUqY6u1_AZ3ER4eVUt';
let datas = JSON.stringify(data);
const headers = { 'Authorization': 'secret' };
return this.http.put(updateUrl + 'data.json', datas, headers)
.catch((e) => {
console.log(e);
})
.finally(() => {
console.log('finally');
})
}
I can see the vote counter update in the logs and on the front, but not in the database. When I refresh the page the vote come back to 0.
I think I just forgot something..
If you want to add a new item to a location, you should use POST instead of PUT:
this.http.post(this.url + 'data.json', datas, headers)...
If you want to update an existing item by only providing partial data , you should use PATCH instead of PUT. If your HTTP client doesn't support sending PATCH, you can use a GET and specify PATCH in the X-HTTP-Method-Override header.
For full details on all of these, read the Firebase documentation in saving data using the REST API.
To update your data, you need to know the key of that particular item to put in your url; for example: your-firebase-url/.../L3-bvgdsggdgde. Something like this
updateData (data:any): Observable<any> {
updateUrl = this.url + 'L3-bvgdsggdgde';
let datas = JSON.stringify(data);
const headers = { 'Authorization': 'secret' };
return this.http.put(updateUrl + 'data.json', datas, headers)
.catch((e) => {
console.log(e);
})
.finally(() => {
console.log('finally');
})
}
So, how to take the key from Firebase and reuse it? following this Firebase doc when you get data from Firebase
constructor(afDb: AngularFireDatabase) {
afDb.list('items').snapshotChanges().map(actions => {
return actions.map(action => ({ key: action.key, ...action.payload.val() }));
}).subscribe(items => {
return items.map(item => item.key);
});
}
Your object will have a key property, reuse it in your upvote/update method.
I've implemented the Push WebAPI in my web application using Service Worker as many articles explain on the web.
Now I need to store some data inside IndexedDB to make them available while the web app is closed (chrome tab closed, service worker in background execution).
In particular I would like to store a simple url from where retrieve the notification data (from server).
Here is my code:
self.addEventListener("push", (event) => {
console.log("[serviceWorker] Push message received", event);
notify({ event: "push" }); // This notifies the push service for handling the notification
var open = indexedDB.open("pushServiceWorkerDb", 1);
open.onsuccess = () => {
var db = open.result;
var tx = db.transaction("urls");
var store = tx.objectStore("urls");
var request = store.get("fetchNotificationDataUrl");
request.onsuccess = (ev) => {
var fetchNotificationDataUrl = request.result;
console.log("[serviceWorker] Fetching notification data from ->", fetchNotificationDataUrl);
if (!(!fetchNotificationDataUrl || fetchNotificationDataUrl.length === 0 || !fetchNotificationDataUrl.trim().length === 0)) {
event.waitUntil(
fetch(fetchNotificationDataUrl, {
credentials: "include"
}).then((response) => {
if (response.status !== 200) {
console.log("[serviceWorker] Looks like there was a problem. Status Code: " + response.status);
throw new Error();
}
return response.json().then((data) => {
if (!data) {
console.error("[serviceWorker] The API returned no data. Showing default notification", data);
//throw new Error();
showDefaultNotification({ url: "/" });
}
var title = data.Title;
var message = data.Message;
var icon = data.Icon;
var tag = data.Tag;
var url = data.Url;
return self.registration.showNotification(title, {
body: message,
icon: icon,
tag: tag,
data: {
url: url
},
requireInteraction: true
});
});
}).catch((err) => {
console.error("[serviceWorker] Unable to retrieve data", err);
var title = "An error occurred";
var message = "We were unable to get the information for this push message";
var icon = "/favicon.ico";
var tag = "notification-error";
return self.registration.showNotification(title, {
body: message,
icon: icon,
tag: tag,
data: {
url: "/"
},
requireInteraction: true
});
})
);
} else {
showDefaultNotification({ url: "/" });
}
}
};
});
Unfortunately when I receive a new push event it doesn't work, showing this exception:
Uncaught DOMException: Failed to execute 'waitUntil' on 'ExtendableEvent': The event handler is already finished.
at IDBRequest.request.onsuccess (https://192.168.0.102/pushServiceWorker.js:99:23)
How can I resolve this?
Thanks in advance
The initial call to event.waitUntil() needs to be done synchronously when the event handler is first invoked. You can then pass in a promise chain to event.waitUntil(), and inside that promise chain, carry out any number of asynchronous actions.
Your current code invokes an asynchronous IndexedDB callback before it calls event.waitUntil(), which is why you're seeing that error.
The easiest way to include IndexedDB operations inside a promise chain is to use a wrapper library, like idb-keyval, which takes the callback-based IndexedDB API and converts it into a promise-based API.
Your code could then look like:
self.addEventListener('push', event => {
// Call event.waitUntil() immediately:
event.waitUntil(
// You can chain together promises:
idbKeyval.get('fetchNotificationDataUrl')
.then(url => fetch(url))
.then(response => response.json())
.then(json => self.registration.showNotification(...)
);
});