SvelteKit: Change UI on incoming Post Request - post

Assume the following:
src/route/create/+server.ts:
import { json } from "#sveltejs/kit";
import type { RequestHandler } from "./$types";
import { testStore } from "$lib/stores/testStore";
export const POST = (async ({ request }) => {
console.log("POST REQUEST INCOMING");
testStore.set("something") << THIS IS NOT WORKING AS EXPECTED
return json({ message: "hi" });
}) satisfies RequestHandler;
Then, in some svelte component:
testStore.subscribe((value) => {
if (value) {
console.log("Post request came in!");
}
});
When I perform a post request, I can see this part "POST REQUEST INCOMING", but the value of the store is not being updated.
In general, I want to do this: The client makes a request to some other Python backend. That backend does something which takes maybe 10 seconds. Once Python is done, it sends a POST request back to the client (the /create endpoint from above), basically saying that the process is complete. Then the client is supposed to change its UI, notifying the user.
Any idea how I can do this?
I've already tried the code above, which didn't work...

Related

Next.js with SSR: How to access cookies to retrieve a JWT and then make a post request with it

Explanation of my problem
So I am currently on a project of my traineeship that requires me to make a small app using Redux to manage the state of the app itself and TanStack Query (a.k.a. React Query v4) to manage asynchronous data from API calls
I am currently using Next.js with Server-Side Rendering (SSR)
And I have a big issue at the moment with one of the pages
So when the user logs in, they're redirected to their profile page, and I want to make a post request with the JSON Web Token stored in cookies to retrieve their info such as their first name and last name
What I did
I used the Next.js function getServerSideProps to make a POST request with the JWT, but because my app uses SSR. I do not have access to cookies and the JWT is stored inside cookies, but I cannot have access to client-side information until the component is fully mounted, also the app crashes when reloading because of that
I used the native React hook useEffect, I declared the variable with the value of the JWT since now I have access to the cookies, but now I cannot use the TanStack Query hook useMutation because you cannot use hooks inside other hooks
(Current implementation) I used a similar approach, but this time declared the JWT variable as undefined along with the constant using the useMutation outside the useEffect and added the JWT variable inside the array of dependencies of the hook, then I retrieved changed the value of the variable to contain the JWT and made a POST request but the post request fails
Here's the code of my current implementation
//React
import { useEffect, useState } from "react";
//Next
import Head from "next/head";
import { NextRouter, useRouter } from "next/router";
//Components
import AccountCard from "#/components/AccountCard/AccountCard";
import Button from "../../components/Button/Button";
//Utils
import { log } from "../../utils/functions/helper-functions";
import { savingsData } from "../../utils/variables/savings-data";
import CookieService from "#/utils/services/cookies.service";
import ApiService from "#/utils/services/api.service";
//Redux
//React-Redux
import { useSelector } from "react-redux";
import { useMutation } from "#tanstack/react-query";
//This is the page of the user
/**
* User page
*
* Route: `/profile/`
* */
export default function Profile(): JSX.Element {
// log({ jsonWebToken });
//We IMPORT the value of the logging state of the user when logging in
const userIsLoggedIn: boolean = useSelector((state: any) => {
return state.isLoggedIn;
});
//We're going to use the router hook to get the current to redirect the user
//if they're not logged in
const router: NextRouter = useRouter();
[...]
let jsonWebToken: string | undefined = undefined;
//We make the POST request
const apiService: ApiService = new ApiService();
const userProfileMutation = useMutation({
mutationFn: (jwt: string) => {
return apiService.postProfile(jwt);
},
onMutate: () => {},
onSuccess: (data, variables) => {
log("SUCCESS, USER INFOS:", data);
},
onError: () => {
log("FAILED TO RETRIEVE USER INFOS");
},
});
//We cannot use the push() method of the router to redirect the user to the sign-in page
//if the user isn't logged in because of the SSR (push is client side only)
useEffect(() => {
//If the user isn't logged in we redirect them to the sign-in page
const userIsNotLoggedIn: boolean = !userIsLoggedIn;
if (userIsNotLoggedIn) {
router.push("/sign-in/");
}
//We recover the jwt inside the browser"s cookies
const cookieCreator: CookieService = new CookieService();
//We initialise it
jsonWebToken = cookieCreator.getCookieByName("jwt")?.value;
log(jsonWebToken);
//#ts-ignore
userProfileMutation.mutate(jsonWebToken);
}, [jsonWebToken]);
return (
<>...</>
);
}
So I'd be very grateful if somebody was able to help me with this issue

How to dispatch a Paypal IPN to a Google Cloud function?

I've read here that it's possible to send an IPN directly to a Google cloud function. I have my Google Cloud functions running on Firebase on an index.js file.
I've set up my Paypal buttons to send the IPN to a page on my webapp.
Here is an example of one of the functions I'm running off Google Cloud Functions/Firebase:
// UPDATE ROOMS INS/OUTS
exports.updateRoomIns = functions.database.ref('/doors/{MACaddress}').onWrite((change, context) => {
const beforeData = change.before.val();
const afterData = change.after.val();
const roomPushKey = afterData.inRoom;
const insbefore = beforeData.ins;
const insafter = afterData.ins;
if ((insbefore === null || insbefore === undefined) && (insafter === null || insafter === undefined) || insbefore === insafter) {
return 0;
} else {
const updates = {};
Object.keys(insafter).forEach(key => {
updates['/rooms/' + roomPushKey + '/ins/' + key] = true;
});
return admin.database().ref().update(updates); // do the update}
}
return 0;
});
Now question:
1) I want to add another function to process IPN from Paypal as soon as I have a transaction. How would I go about this?
I'll mark the answer as correct if solves this first question.
2) how would that Google cloud function even look like?
I'll create another question if you can solve this one.
Note I am using Firebase (no other databases nor PHP).
IPN is simply a server that tries to reach a given endpoint.
First, you have to make sure that your firebase plan supports 3rd party requests (it's unavailable in the free plan).
After that, you need to make an http endpoint, like so:
exports.ipn = functions.http.onRequest((req, res) => {
// req and res are instances of req and res of Express.js
// You can validate the request and update your database accordingly.
});
It will be available in https://www.YOUR-FIREBASE-DOMAIN.com/ipn
Based on #Eliya Cohen answer:
on your firebase functions create a function such as:
exports.ipn = functions.https.onRequest((req, res) => {
var reqBody = req.body;
console.log(reqBody);
// do something else with the req.body i.e: updating a firebase node with some of that info
res.sendStatus(200);
});
When you deploy your functions go to your firebase console project and check your functions. You should have something like this:
Copy that url, go to paypal, edit the button that's triggering the purchase, scroll down to Step 3 and at the bottom type:
notify_url= paste that url here
Save changes.
You can now test your button and check the req.body on your firebase cloud functions Log tab.
Thanks to the answers here, and especially to this gist: https://gist.github.com/dsternlicht/fdef0c57f2f2561f2c6c477f81fa348e,
.. finally worked out a solution to verify the IPN request in a cloud func:
let CONFIRM_URL_SANDBOX = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr';
exports.ipn = functions.https.onRequest((req, res) => {
let body = req.body;
logr.debug('body: ' + StringUtil.toStr(body));
let postreq = 'cmd=_notify-validate';
// Iterate the original request payload object
// and prepend its keys and values to the post string
Object.keys(body).map((key) => {
postreq = `${postreq}&${key}=${body[key]}`;
return key;
});
let request = require('request');
let options = {
method: 'POST',
uri : CONFIRM_URL_SANDBOX,
headers: {
'Content-Length': postreq.length,
},
encoding: 'utf-8',
body: postreq
};
res.sendStatus(200);
return new Promise((resolve, reject) => {
// Make a post request to PayPal
return request(options, (error, response, resBody) => {
if (error || response.statusCode !== 200) {
reject(new Error(error));
return;
}
let bodyResult = resBody.substring(0, 8);
logr.debug('bodyResult: ' + bodyResult);
// Validate the response from PayPal and resolve / reject the promise.
if (resBody.substring(0, 8) === 'VERIFIED') {
return resolve(true);
} else if (resBody.substring(0, 7) === 'INVALID') {
return reject(new Error('IPN Message is invalid.'));
} else {
return reject(new Error('Unexpected response body.'));
}
});
});
});
Also thanks to:
https://developer.paypal.com/docs/classic/ipn/ht-ipn/#do-it
IPN listener request-response flow: https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNImplementation/
To receive IPN message data from PayPal, your listener must follow this request-response flow:
Your listener listens for the HTTPS POST IPN messages that PayPal sends with each event.
After receiving the IPN message from PayPal, your listener returns an empty HTTP 200 response to PayPal. Otherwise, PayPal resends the IPN message.
Your listener sends the complete message back to PayPal using HTTPS POST.
Prefix the returned message with the cmd=_notify-validate variable, but do not change the message fields, the order of the fields, or the character encoding from the original message.
Extremely late to the party but for anyone still looking for this, PayPal have made a sample in their JS folder on their IPN samples Github repo.
You can find this at:
https://github.com/paypal/ipn-code-samples/blob/master/javascript/googlecloudfunctions.js

Zapier JS Action to Fetch Klout Scores

I'm trying to create a Java Script Code Action on Zapier to fetch Klout Scores for any given Twitter user name...
I've realized that this needs to be done in 2 stages:
1) First get the Klout ID for any Twitter screen_name:
http://api.klout.com/v2/identity.json/twitter?screenName="+screen_name+"&key="+klout_apikey"
Klout replies back to that with JSon:
{"id":"85568398087870011","network":"ks"}
2) second get the Klout score for that Klout id:
http://api.klout.com/v2/user.json/"+klout.id+"/score?key="+klout_apikey"
Klout replies back to this with JSon:
{"score":65.68382904221806,"scoreDelta":{"dayChange":-0.03663891859041257,"weekChange":-0.5495711661078815,"monthChange":-1.4045672671990417},"bucket":"60-69"}
Of course, what I need is the "score":65.68382904221806 object of the JSon reply array.
I use these following JS functions proposed by #KayCee:
var klout_apikey = '<my klout api key>';
fetch("http://api.klout.com/v2/identity.json/twitter?screenName="+screen_name+"&key="+klout_apikey")
.then(function(res) {
return res.json();
})
.then(function(klout) {
console.log(klout);
if(klout.id) {
return fetch("http://api.klout.com/v2/user.json/"+klout.id+"/score?key="+klout_apikey")
}
}).then(function(res) {
return res.json();
}).then(function(body) {
// console.log(body.score);
//Here is where you are telling Zapier what you want to output.
callback(null, body.score)
}).catch(callback); //Required by Zapier for all asynchronous functions.
In the "input data" section of the Zapier code action i pass the screen_name as a variable:
screen_name: [the twitter handle]
What I get back is the following error message:
SyntaxError: Invalid or unexpected token
What is the error that you see? You could do this by simply using the fetch client. You might want to remove the variable declarations before adding this to the code step.
var inputData = {'screen_name': 'jtimberlake'}
//Remove the line above before pasting in the Code step. You will need to configure it in the Zap.
var klout_apikey = '2gm5rt3hsdsdrzgvnskmgm'; //Not a real key
fetch("http://api.klout.com/v2/identity.json/twitter?screenName="+inputData.screen_name+"&key="+klout_apikey)
.then(function(res) {
return res.json();
})
.then(function(body) {
console.log(body);
if(body.id) {
return fetch("http://api.klout.com/v2/user.json/"+body.id+"/score?key="+klout_apikey)
}
}).then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
//Here is where you are telling Zapier what you want to output.
callback(null, body)
}).catch(callback); //Required by Zapier for all asynchronous functions.
Refer to their documentation here - https://zapier.com/help/code/#introductory-http-example
Also refer to their Store client which allows you to store values (for cache) - https://zapier.com/help/code/#storeclient-javascript

Passing very large XML data as params in Angular 2 to Rails

I have a text area, where user can paste any XML. Then on click of a button, the XML is sent to the server to get the response. I find, this works perfectly with smaller XMLs. But, my issue is, when I send a large XML, the Rails server log simply says
!! Invalid
request
CODE
Provider.ts
import { Injectable } from '#angular/core';
import { Http, Headers, Response } from '#angular/http';
import { HttpClientService } from './../services/httpclient.service';
#Injectable()
export class QuestWebServiceTestProvider {
constructor(private http: HttpClientService) {}
getResponseXML(params) {
return this.http.get('/api/v1/get_response_xml?request_xml=' + params["request_xml"]);
}
}
Component.ts (this passes the XML read from textarea to provider)
getResponse() {
let requestXml = this.requestXmlElement.nativeElement.value.trim();
if(requestXml.length > 0) {
let requestXmlJson = encodeURI(requestXml);
this.<provider>.getResponseXML({request_xml: requestXmlJson).subscribe(
data => {
console.log(data);
}
)
}
}
What is the best way to send larger XMLs?
Sorry, for the typo in the question. I actually use POST request.
Thanks Pavel Mikhailyuk for pointing it.
This is how I fixed it:
Instead of framing the query params manually as:
...get_response_xml?request_xml=' + params["request_xml"]
I pass the XML as options to HTTP POST:
let options = {request_xml: params["request_xml"] };
return this.http.post('...get_response_xml?, options);
This seemed to work !!!

Pass custom data to service worker sync?

I need to make a POST request and send some data. I'm using the service worker sync to handle offline situation.
But is there a way to pass the POST data to the service worker, so it makes the same request again?
Cause apparently the current solution is to store requests in some client side storage and after client gets connection - get the requests info from the storage and then send them.
Any more elegant way?
PS: I thought about just making the service worker send message to the application code so it does the request again ... but unfortunately it doesn't know the exact client that registered the service worker :(
You can use fetch-sync
or i use postmessage to fix this problem, which i agree that indexedDB looks trouble.
first of all, i send the message from html.
// send message to serviceWorker
function sync (url, options) {
navigator.serviceWorker.controller.postMessage({type: 'sync', url, options})
}
i got this message in serviceworker, and then i store it.
const syncStore = {}
self.addEventListener('message', event => {
if(event.data.type === 'sync') {
// get a unique id to save the data
const id = uuid()
syncStore[id] = event.data
// register a sync and pass the id as tag for it to get the data
self.registration.sync.register(id)
}
console.log(event.data)
})
in the sync event, i got the data and fetch
self.addEventListener('sync', event => {
// get the data by tag
const {url, options} = syncStore[event.tag]
event.waitUntil(fetch(url, options))
})
it works well in my test, what's more you can delete the memory store after the fetch
what's more, you may want to send back the result to the page. i will do this in the same way by postmessage.
as now i have to communicate between each other, i will change the fucnction sync into this way
// use messagechannel to communicate
sendMessageToSw (msg) {
return new Promise((resolve, reject) => {
// Create a Message Channel
const msg_chan = new MessageChannel()
// Handler for recieving message reply from service worker
msg_chan.port1.onmessage = event => {
if(event.data.error) {
reject(event.data.error)
} else {
resolve(event.data)
}
}
navigator.serviceWorker.controller.postMessage(msg, [msg_chan.port2])
})
}
// send message to serviceWorker
// you can see that i add a parse argument
// this is use to tell the serviceworker how to parse our data
function sync (url, options, parse) {
return sendMessageToSw({type: 'sync', url, options, parse})
}
i also have to change the message event, so that i can pass the port to sync event
self.addEventListener('message', event => {
if(isObject(event.data)) {
if(event.data.type === 'sync') {
// in this way, you can decide your tag
const id = event.data.id || uuid()
// pass the port into the memory stor
syncStore[id] = Object.assign({port: event.ports[0]}, event.data)
self.registration.sync.register(id)
}
}
})
up to now, we can handle the sync event
self.addEventListener('sync', event => {
const {url, options, port, parse} = syncStore[event.tag] || {}
// delete the memory
delete syncStore[event.tag]
event.waitUntil(fetch(url, options)
.then(response => {
// clone response because it will fail to parse if it parse again
const copy = response.clone()
if(response.ok) {
// parse it as you like
copy[parse]()
.then(data => {
// when success postmessage back
port.postMessage(data)
})
} else {
port.postMessage({error: response.status})
}
})
.catch(error => {
port.postMessage({error: error.message})
})
)
})
At the end. you cannot use postmessage to send response directly.Because it's illegal.So you need to parse it, such as text, json, blob, etc. i think that's enough.
As you have mention that, you may want to open the window.
i advice that you can use serviceworker to send a notification.
self.addEventListener('push', function (event) {
const title = 'i am a fucking test'
const options = {
body: 'Yay it works.',
}
event.waitUntil(self.registration.showNotification(title, options))
})
self.addEventListener('notificationclick', function (event) {
event.notification.close()
event.waitUntil(
clients.openWindow('https://yoursite.com')
)
})
when the client click we can open the window.
To comunicate with the serviceworker I use a trick:
in the fetch eventlistener I put this:
self.addEventListener('fetch', event => {
if (event.request.url.includes("sw_messages.js")) {
var zib = "some data";
event.respondWith(new Response("window.msg=" + JSON.stringify(zib) + ";", {
headers: {
'Content-Type': 'application/javascript'
}
}));
}
return;
});
then, in the main html I just add:
<script src="sw_messages.js"></script>
as the page loads, global variable msg will contain (in this example) "some data".

Resources