Fetch API - Targeting an Iframe - javascript

Using the target attribute of an html form, you can post form data and then have the html-server-response showcased in an iframe.
form.setAttribute("target", "nameOfIframe");
Can the same thing be achieved posting with the ecmascript fetch api (without creating an actual html form element)?

It's fairly trivial to update the content of an IFRAME with JavaScript/EcmaScript and can be done using the srcdoc property.
document.getElementById('fetchTarget').srcdoc = `<!DOCTYPE html><p>Hello World!</p>`;
<iframe id="fetchTarget">
</iframe>
All you would need to do is arrange to update it as part of the processing of the call to fetch:
fetch(`https://jsonplaceholder.typicode.com/posts/1`)
.then(response => {
console.log(response);
if (response.ok){
return response.text();
}
})
.then(text => {
document.getElementById('fetchTarget').srcdoc = text;
});
<iframe id="fetchTarget">
</iframe>
This URL isn't a particularly great example as it returns JSON instead of HTML but it doesn't really affect the result.

I was able to get it the fetch POST response into the iframe by using the srcdoc property of the iframe, as advised to me by this AI answer.
async function sendFetchResponseToIframe()
{
const formData = new FormData();
formData.append('input1', 'value1');
formData.append('input2', 'value2');
let options = {};
options.method = 'POST';
options.mode = 'cors';
options.body = formData;
let postURL = 'http://neartalk.com/test/formData.php';
let response = await fetch(postURL, options);
let data = await response.text();
let iframe = document.getElementById("frame1");
iframe.srcdoc = data;
}
sendFetchResponseToIframe();
<p>The fieldset below contains an iframe that gets updated to the Fetch POST response:</p>
<fieldset>
<legend>Iframe</legend>
<iframe id="frame1" style="width:100%" src="http://www.example.com/" frameborder="0"></iframe>
</fieldset>
However, this method is not as transparent as setting the target of an HTML form, which preserves all relative links that may be returned by a server's HTML response.
While there are methods of addressing this, like: (1) Modifying the html response to include a base element, or (2) modifying the HTML response to convert all relative links to absolute links. I can still imagine other complications like server-side redirects that could ultimately occur. Such redirects would lead to you ultimately having to also modifying your fetch code to follow the redirects, determine the final URL, and convert the base URL or absolute links accordingly.
Due to these complications, the answer to my question is ultimately: No; you're better off creating a hidden HTML form dynamically, and setting its target prior to submission, and then ultimately removing that dynamically created form after submission. This method more easily protects the relative links that could potentially be in the HTML response:
function submitFormToIframe(actionURL, formData, iframe)
{
const form = document.createElement('form');
form.action = actionURL;
form.method = 'POST';
form.target = iframe.name;
for (const [name, value] of formData.entries())
{
const input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = value;
form.appendChild(input);
}
form.style.display = "none";
document.body.append(form);
form.submit();
document.body.removeChild(form);
}
async function main()
{
// Create Form Data:
const formData = new FormData();
formData.append('input1', 'value1');
formData.append('input2', 'value2');
// Get iframe from DOM:
let iframe = document.getElementById("frame1");
let actionURL = 'http://neartalk.com/test/formData.php';
submitFormToIframe(actionURL, formData, iframe);
}
onload = main;
<p>The fieldset below contains an iframe that gets updated via generated form submission:</p>
<fieldset>
<legend>Iframe</legend>
<iframe id="frame1" name="frame1" style="width:100%" src="http://www.example.com/" frameborder="0"></iframe>
</fieldset>
Due to these findings, I think the specification writers, should consider adding a fetch option that allows you to send a fetch response directly to a specified iframe, in a manner where relative links aren't susceptible to breakage (as is the case with HTMLFormElement.target. This would make the fetch API a more attractive solution.

If you need to process the data inside the iframe, you can use window.postMessage(). Instead of iframe.srcdoc property, you can of course put the code in a separate file and reference it via iframe.src
let btn = document.getElementById('btn')
let otherBtn = document.getElementById('btn2')
let wrongBtn = document.getElementById('btn3')
let results = document.getElementById('results')
function showResults(json) {
results.contentWindow.postMessage({
action: 'showResults',
data: json,
}, "*");
}
function sendForm() {
fetch('https://dummyjson.com/products/1')
.then(res => res.json())
.then(showResults)
}
function addLinkToIframe() {
results.contentWindow.postMessage({
action: 'addLink',
data: {
href: 'index.html',
text: 'Click me!',
},
}, "*");
}
function addCallNonExistentAction() {
results.contentWindow.postMessage({
action: 'gibberish',
data: 'even more gibberish',
}, "*");
}
btn.addEventListener('click', sendForm)
otherBtn.addEventListener('click', addLinkToIframe)
wrongBtn.addEventListener('click', addCallNonExistentAction)
window.addEventListener('message', (event) => {
if (event.data.action === 'iframe::DOMContentLoaded') {
console.log('iframe loaded');
results.contentWindow.postMessage({
action: 'setBase',
data: document.baseURI,
}, "*");
} else {
alert(`Action '${event.data.action}' not defined`)
}
})
<button id="btn">Send</button>
<button id="btn2">Add link</button>
<button id="btn3">Call non-existent action</button>
<hr />
<iframe id="results" srcdoc="<!DOCTYPE html>
<html>
<head>
<base href=>
<script>
window.addEventListener('message', (event) => {
if (event.data.action === 'showResults') {
document.body.querySelector('pre').appendChild(
document.createTextNode(JSON.stringify(event.data.data, true, '\t'))
);
} else if (event.data.action === 'setBase') {
console.log('setbase')
document.head.querySelector('base').href = event.data.data
console.log(document.baseURI)
} else if (event.data.action === 'addLink') {
link = Object.assign(document.createElement('a'), {
href: event.data.data.href,
})
link.textContent = event.data.data.text
console.log('add', link)
document.body.prepend(link);
} else {
alert(`Action '${event.data.action}' not defined`)
}
})
window.parent.postMessage({
action: 'iframe::DOMContentLoaded',
}, '*')
</script>
</head>
<body>
Waiting ...
<pre></pre>
</body>
</html>
"></iframe>

Related

Authorize.net embedded iFrame accept.js response transaction

I have integrated authorize.net accept.js embedded iFrame in my application. Having trouble setting the transaction respone in my lambda function to get the response. I've seen similar questions on stack overflow but nothing worked out for me yet.
Using Nodejs for my backend and angular7 for the front-end.
I successfully get the token from my lambda function so my iframe appears on the ui. I've set \"showReceipt\": false providing url for cancel & continue, since the documentation says I have to set the show receipt parameter "false" in order to communicate with the IFrameCommunicator.html in the ui. But when I click on "Pay" its stuck at "Processing.." for a long time.
Following are the request & response headers respectively from the network tab:
* Cookie: __cfruid=deb63d2f12d9690aeea838cf7f31ada6da92bc1c-1602260930
* Host: test.authorize.net
* Origin: https://test.authorize.net
* Referer: https://test.authorize.net/payment/payment
* Sec-Fetch-Dest: empty
* Sec-Fetch-Mode: cors
* 
Sec-Fetch-Site: same-origin
{"resultCode":"Ok","messageCode":"Ok","transactionData":{"accountType":"Discover","accountNumber":"XXXX0012","transId":"40055282319","responseCode":"4","authorization":"TYIUU7","merchantName":"iMart Inc.","totalAmount":"1999.9","dateTime":"10/09/2020 4:20:27 PM"}}
I'm sure the transaction is happening looking at the response but not sure why it's not connecting with the communicator.
I've read the steps in the documentation and also followed the GitHub sample code-https://github.com/AuthorizeNet/accept-sample-app, which made me more confused since they both say different things at some places. Following are the steps I've accomplished until now :
Created a lambda hosted payment function with all the settings (followed the correct sequence) to get back a token.
Created a hosted payment form to display the iframe.
Able to make a payment --> get the receipt page --> routing to success screen.
What I'm trying to accomplish:
After I make the payment, initial idea was to trigger a different lambda function based on the response from authorize.net without communicating with IFrameCommunicator.html, but as I cannot do that, I want to get a response to initiate the next process at the backend.
Also, we're not storing any user details in our server and not interested in creating a customer profile unless it's a necessary step to get the transaction response. Please suggest the step integration if I can do it in the same lambda function I've created to get a token or I would have to create a different one for this and when will that step be implemented?
I know about the Webhooks but not sure if it's an absolute necessity at this point of time when I'm just trying to implement a simple transaction.
I'm a newbie and I couldn't find a lot of examples related to the same to resolve my issues/confusions. Would highly appreciate if I get a clear explanation on the steps here and where am I going wrong.
Following is the code -
accept-hosted.js Lambda function:
merchantAuthenticationType.setName('*****');
merchantAuthenticationType.setTransactionKey('******');
var transactionRequestType = new ApiContracts.TransactionRequestType();
transactionRequestType.setTransactionType(ApiContracts.TransactionTypeEnum.AUTHCAPTURETRANSACTION);
transactionRequestType.setAmount(Total);
var setting1 = new ApiContracts.SettingType();
var setting2 = new ApiContracts.SettingType();
var setting4 = new ApiContracts.SettingType();
var setting5 = new ApiContracts.SettingType();
var setting6 = new ApiContracts.SettingType();
var setting7 = new ApiContracts.SettingType();
var setting8 = new ApiContracts.SettingType();
var setting9 = new ApiContracts.SettingType();
var setting10 = new ApiContracts.SettingType();
var setting11 = new ApiContracts.SettingType();
setting2.setSettingName("hostedPaymentButtonOptions");
setting2.setSettingValue("{\"text\": \"Pay\"}");
setting1.setSettingName("hostedPaymentReturnOptions");
setting1.setSettingValue(
"{\"showReceipt\": false, \"url\": \"https://iMart.com/success.html\", \"urlText\": \"Continue\", \"cancelUrl\": \"https://iMart.com/error.html\", \"cancelUrlText\": \"Cancel\"}");
setting10.setSettingName("hostedPaymentOrderOptions");
setting10.setSettingValue("{\"show\": false, \"merchantName\": \"iMart Inc.\"}");
setting5.setSettingName("hostedPaymentPaymentOptions");
setting5.setSettingValue("{\"cardCodeRequired\": true, \"showCreditCard\": true, \"showBankAccount\": false}");
setting7.setSettingName("hostedPaymentShippingAddressOptions");
setting7.setSettingValue("{\"show\": false, \"required\": false}");
setting8.setSettingName("hostedPaymentBillingAddressOptions");
setting8.setSettingValue("{\"show\": false, \"required\": false}");
setting6.setSettingName("hostedPaymentSecurityOptions");
setting6.setSettingValue("{\"captcha\": true}");
setting4.setSettingName("hostedPaymentStyleOptions");
setting4.setSettingValue("{\"bgColor\": \"blue\"}");
setting9.setSettingName("hostedPaymentCustomerOptions");
setting9.setSettingValue("{\"showEmail\": false, \"requiredEmail\": false, \"addPaymentProfile\": true }");
setting11.setSettingName("hostedPaymentIFrameCommunicatorUrl");
setting11.setSettingValue("{\"url\": \"https://iMart.com/IFrameCommunicator.html\"}");
var settingList = [];
settingList.push(setting2);
settingList.push(setting10);
settingList.push(setting5);
settingList.push(setting7);
settingList.push(setting8);
settingList.push(setting6);
settingList.push(setting4);
settingList.push(setting9);
settingList.push(setting11);
settingList.push(setting1);
var alist = new ApiContracts.ArrayOfSetting();
alist.setSetting(settingList);
var firstname = new ApiContracts.UserField();
firstname.setName('First Name');
firstname.setValue(firstName);
var lastname = new ApiContracts.UserField();
lastname.setName('Last Name');
lastname.setValue(lastName);
var userFieldList = [];
userFieldList.push(firstname);
userFieldList.push(lastname);
var userFields = new ApiContracts.TransactionRequestType.UserFields();
userFields.setUserField(userFieldList);
var transactionSetting1 = new ApiContracts.SettingType();
transactionSetting1.setSettingName('duplicateWindow');
transactionSetting1.setSettingValue('120');
var transactionSetting2 = new ApiContracts.SettingType();
transactionSetting2.setSettingName('recurringBilling');
transactionSetting2.setSettingValue('false');
var transactionSetting3 = new ApiContracts.SettingType();
transactionSetting3.setSettingName('emailCustomer');
transactionSetting3.setSettingValue('true');
var transactionSetting4 = new ApiContracts.SettingType();
transactionSetting4.setSettingName('headerEmailReceipt');
transactionSetting3.setSettingValue('You are all set!');
var transactionSetting5 = new ApiContracts.SettingType();
transactionSetting5.setSettingName('footerEmailReceipt');
transactionSetting5.setSettingValue('This is the footer');
var getRequest = new ApiContracts.GetHostedPaymentPageRequest();
getRequest.setMerchantAuthentication(merchantAuthenticationType);
getRequest.setTransactionRequest(transactionRequestType);
getRequest.setHostedPaymentSettings(alist);
var ctrl = new ApiControllers.GetHostedPaymentPageController(getRequest.getJSON());
const basicAuth = encode.encode("*****", 'base64');
await axios({
method: 'post',
url: 'https://apitest.authorize.net/xml/v1/request.api',
headers: {
'Authorization': 'Basic '+basicAuth,
'Content-Type': 'application/json'
},
data:JSON.stringify(ctrl._request)
}).then(async (data : any)=>{
if(data.data.token) {
callback(null, data.data) ;
} else callErr(data);
});
async function callErr(data: any){
callback(null, res) ;
}
}
IFrameCommunicator.html:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Iframe Communicator</title>
<script type="text/javascript">
//<![CDATA[
function callParentFunction(str) {
if (str && str.length > 0
&& window.parent
&& window.parent.parent
&& window.parent.parent.AuthorizeNetPopup
&& window.parent.parent.AuthorizeNetPopup.onReceiveCommunication)
{
// Errors indicate a mismatch in domain between the page containing the iframe and this page.
window.parent.parent.AuthorizeNetPopup.onReceiveCommunication(str);
}
}
function receiveMessage(event) {
if (event && event.data) {
callParentFunction(event.data);
}
}
if (window.addEventListener) {
console.log('addEventListener');
console.log(receiveMessage);
window.addEventListener("message", receiveMessage, false);
} else if (window.attachEvent) {
window.attachEvent("onmessage", receiveMessage);
}
if (window.location.hash && window.location.hash.length > 1) { callParentFunction(window.location.hash.substring(1));
}
//]]/>
</script>
</head>
<body>
</body>
</html>
Angular code for showing the iFrame:
<iframe id="add_payment" class="embed-responsive-item panel" name="add_payment" width="100%" frameborder="0" scrolling="yes">
</iframe>
</div>
<form id="send_token" action="" method="post" target="add_payment" >
<input id="token" type="hidden" name="token" />
</form>
I have been struggling a lot since many days now with a time crunch. Would be really helpful if someone provides me with a good insight here. Please let me know if additional info is required. Thank you in advance!!!
Here are the answer for all your question, I hope it works :
1)if you are using iFrame then iFrameCommunicator is mandatory
2)the success url can only be used when you set "showReceipt" as true, here you cannot navigate automatically to yoour success page, this is the link for "Continue" button which appears when "showReceipt" is allowed
3)If you want to trigger any function or want to navigate after the response then add the following code in your html file
<script type="text/javascript">
$(document).ready(function () {
window.CommunicationHandler = {};
function parseQueryString(str) {
var vars = [];
var arr = str.split('&');
var pair;
for (var i = 0; i < arr.length; i++) {
pair = arr[i].split('=');
vars[pair[0]] = unescape(pair[1]);
}
return vars;
}
window.CommunicationHandler.onReceiveCommunication = function (argument) {
console.log('communication handler enter', argument);
var params = parseQueryString(argument.qstr)
switch (params['action']) {
case "resizeWindow":
console.log('resize'); break;
case "successfulSave":
console.log('save'); break;
case "cancel":
console.log('cancel'); break;
case "transactResponse":
sessionStorage.removeItem("HPTokenTime");
console.log('transaction complete');
var transResponse = JSON.parse(params['response']);
console.log('transaction complete1', transResponse);
// window.location.href = '/checkout/complete';
}
}
//send the token
$('#send_hptoken').submit();
});
</script>

How to redirect to another page through JS (sending parameters)

I'm trying to redirect to another page through JS(sending parameters)
I made the following code to that purpose, it does redirect and the requested page loads up 'ok'
window.location.replace(`http://10.0.30.11:3000/ProteseWEB/pages/detail/${path}?id=${param}`)
The problem is: When the requested page opens, it display an 404 not found on the network tab.
I don't understand why this is occuring, since the page I want is loaded 'perfectly'
The error on the network tab:
Edit(1)
#jlemley and #James Thank you for your answer!!!
Indeed the 404 request and the URL that I requested were different. So I changed the function a little bit.
Here's the code:
openPageDetail = function (path, params, method = 'POST') {
let iplocal = 'http://10.0.30.11:3000/ProteseWEB/pages/detalhe/'
const form = document.createElement('form');
form.method = method;
form.action = iplocal + path;
for (const key in params) {
if (params.hasOwnProperty(key)) {
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.name = key;
hiddenField.value = params[key];
form.appendChild(hiddenField);
}
}
document.body.appendChild(form);
form.submit();
}
I tried to make it as a form submit, still, the error occurs
In addition, there's how I call for this function.
I have a DataTable and when the user double click a row, it opens up a detail page for the desired record.
$('.table').on('dblclick', function(e) {
if ((!(e.target.classList.contains('sorting_asc'))) && (!(e.target.classList.contains('sorting_desc')))) {
openPageDetail('contas-a-pagar', {
id: e.target.parentElement.id
});
}
});

Form onsubmit in dynamic iframe do not get triggered

Update
the problem is not the iframe, the problem is the form not submitting by the onsubmit function that posts json. The goal is to dynamically create an iframe that redirects using a form post to another URL with the json content of the script tag above.
Original
I have the following on a sample website:
<script data-dashboard="true" type="application/json">
{
"title":"Serverless Identity"
}
</script>
<script type="text/javascript">
let dashboardConfiguration = document.querySelector("script[data-dashboard=\"true\"]");
if (dashboardConfiguration) {
let iframe = document.createElement("iframe");
let model = JSON.stringify(JSON.parse(dashboardConfiguration.innerHTML.trim()));
document.body.appendChild(iframe);
var doc = iframe.contentWindow.document;
doc.open()
doc.writeln(`<form id="form" action="https://localhost:44338/dashboard/" method="POST" target="_self"></form>`)
doc.close();
iframe.onload = () => {
let form = doc.getElementById("form");
form.addEventListener("submit", (e) => {
console.log(model);
e.preventDefault();
// construct an HTTP request
var xhr = new XMLHttpRequest();
xhr.open(form.method, form.action, true);
xhr.setRequestHeader('content-type', 'application/json; charset=UTF-8');
// send the collected data as JSON
xhr.send(model);
xhr.onloadend = function () {
// done
};
});
form.submit();
}
};
</script>
I also tried with onsubmit with the same result that it does a normal submit.
Its not possible,
when using form.submit() any onsubmit handlers will not trigger, as per spec.
I used a different approach og sending a normal form submit using form encoded values with the hole payload in one hidden field and deserialized it on serverside.
When you have created iframe then that it is loaded and then you are binding onload instead of that you should use DOMNodeInserted event on body when iframe is appended to the body like
let iframe = document.createElement("iframe");
let model = JSON.stringify(JSON.parse(dashboardConfiguration.innerHTML.trim()));
document.body.addEventListener("DOMNodeInserted", function (ev) {
//write your logic here
});
document.body.appendChild(iframe);
var doc = iframe.contentWindow.document;
Remove iframe.onload = () => { }. Check the chrome network tab. The form url https://localhost:44338/dashboard/ gets triggered.
<script data-dashboard="true" type="application/json">
{
"title":"Serverless Identity"
}
</script>
<script type="text/javascript">
let dashboardConfiguration = document.querySelector("script[data-dashboard=\"true\"]");
if (dashboardConfiguration) {
let iframe = document.createElement("iframe");
let model = JSON.stringify(JSON.parse(dashboardConfiguration.innerHTML.trim()));
document.body.appendChild(iframe);
var doc = iframe.contentWindow.document;
doc.open()
doc.writeln(`<form id="form" action="https://localhost:44338/dashboard/" method="POST" target="_self"></form>`)
doc.close();
let form = doc.getElementById("form");
form.addEventListener("submit", (e) => {
console.log(model);
e.preventDefault();
// construct an HTTP request
var xhr = new XMLHttpRequest();
xhr.open(form.method, form.action, true);
xhr.setRequestHeader('content-type', 'application/json; charset=UTF-8');
// send the collected data as JSON
xhr.send(model);
xhr.onloadend = function () {
// done
};
});
form.submit();
}

How to send data in HTTP POST request while calling a url?

I wrote a simple jsp page where I am loading an external website inside one of my division.
Below is the code :
<html>
<head>
<script src="../common/jquery-1.6.2.min.js"></script>
<script>
$(document).ready(function(){
$("#menu").html('<object data="www.someurl.com">').appendTo('body');;
});
</script>
</head>
<body>
<div id="menu" style="position:relative; bottom: 0; overflow:hidden;">
</div>
</body>
</html>
Basically what I am doing is that , I am setting an HTML content having a src URL inside the object tag.
This is successful and I can load web URL inside my division.
Question comes when I want to send some data(may be a single variable) using HTTP POST method.
Is there any way to call the same URL using HTTP POST ?
Note : Here I cannot use Jquery POST because of same origin policy.
Try to load the external link or website in an Iframe and try.
<div>
<iframe id="" name="" src="your external link" ></iframe>
</div>
I dont know if you an use php,becouse of the no php tag,but you can post data using HttpRequest class in php,and it is secured !
here is a link : http://php.net/manual/en/class.httprequest.php
Take a look at the Jquery post function: http://api.jquery.com/jQuery.post/
Also, if you want to send some data, along with the url, you can do it with your get as well:
www.someurl.com?dataAttr=someValue&dataOtherAttr=someOtherDataValue
This would be the GET equivalent of a post with the following data:
{
"dataAttr": "someValue",
"dataOtherAttr": "someOtherDataValue"
}
Here is a pure js that will make a post to a server. The script makes an invisible iframe and makes a call to a specified server with the specified parameters. The server has to support the Cross Domain Policy. If you can't make the server support CORS, you are out of luck:
/**
*
* Makes a post to the specified url with the specified param - keyval
*/
makePost = function makePost(url, params){
var iframeId = 'iframeid';
var addParamsToForm = function (form, params){
var addParamToForm = function(form, paramName, paramValue){
var input = document.createElement('input');
input.hidden = 'hidden';
input.name = paramName;
input.value = paramValue;
form.appendChild(input);
}
for ( var prop in params ){
if ( params.hasOwnProperty(prop) ){
if ( params[prop] instanceof Array ){
for ( var i = 0; i < params[prop].length; i ++ ){
addParamToForm(form, prop, params[prop][i]);
}
} else {
addParamToForm(form, prop, params[prop]);
}
}
}
};
var iframe = document.getElementById(iframeId);
if ( iframe === null ){
iframe = document.createElement('iframe');
iframe.name = 'iframeName';
iframe.id = iframeId;
iframe.setAttribute("style", "width: 0; height: 0; border: none; display: none;");
}
var form = document.createElement('form');
form.action = url;
form.method = 'POST';
form.target = iframe.name;
addParamsToForm(form, params);
iframe.appendChild(form);
document.getElementsByTagName('body')[0].appendChild(iframe);
form.submit();
}
example usage:
makePost('yourserver', {'someAttr':'someAttrValue', 'someOtherAttr': 'someOtherAttrValue'});
Or a jquery variant:
$.ajax({
type: 'POST',
url: 'yourserver',
crossDomain: true,
data: {'someAttr':'someAttrValue', 'someOtherAttr': 'someOtherAttrValue'},
dataType: 'json',
success: function(responseData, textStatus, jqXHR) {
var value = responseData.someKey;
},
error: function (responseData, textStatus, errorThrown) {
alert('POST failed.');
}
});
On tips how to configure your server to support CORS:
http://enable-cors.org/server.html
Take a look at this one:
How do I send a cross-domain POST request via JavaScript?

Chrome Extension Development - POST to new tab

Is there an easy solution to POST dynamically aggregated data into a new tab?
chrome.tabs.create does not have a 'POST' option. Normally I would use
chrome.browserAction.onClicked.addListener(function (t) {
chrome.tabs.create(
{
"url" : "http://super.url",
"method" : "POST" // oops.. no option.
});
});
You can simply combine these two techniques:
You may execute JavaScript commands by adding javascript: prefix at your address bar or in href values of <a> tags.
Only with JavaScript, you can create a form element and fill it with your data then POST it.
function fakePost() {
var form = document.createElement("form");
// Create a POST dump at ptsv2.com and change the code
form.setAttribute("action", "http://ptsv2.com/t/dcgex-1614815819/post");
form.setAttribute("method", "post");
var params = { userId: 2, action: "delete" };
for(var key in params) {
var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", key);
hiddenField.setAttribute("value", params[key]);
form.appendChild(hiddenField);
}
// Appending the form might not be necessary
document.body.appendChild(form);
form.submit();
};
const
source = fakePost.toString().replace(/(\n|\t)/gm,'').replace(/\s\s/gm,' '),
url = `javascript:${source}; fakePost();`;
chrome.browserAction.onClicked.addListener(() => chrome.tabs.create({ url }));
Of course, that's just a dirty hack. If you need something more elaborate you can use a XHR Object or #Xan's answer.
The code in cvsguimaraes' answer works for short data strings, that can fit into a URL.
As evidenced by this question, it's not always the case.
Kenny Evitt's answer hints at the solution. I made an implementation for that question, and took time to generalize it. I present it here.
The idea is to open a page bundled with the extension (post.html), supply it with required information via messaging, and perform the POST from that page.
post.html
<html>
<head>
<title>Redirecting...</title>
</head>
<body>
<h1>Redirecting...</h1>
<!-- Decorate as you wish, this is a page that redirects to a final one -->
<script src="post.js"></script>
</body>
</html>
post.js
var onMessageHandler = function(message){
// Ensure it is run only once, as we will try to message twice
chrome.runtime.onMessage.removeListener(onMessageHandler);
// code from https://stackoverflow.com/a/7404033/934239
var form = document.createElement("form");
form.setAttribute("method", "post");
form.setAttribute("action", message.url);
for(var key in message.data) {
var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", key);
hiddenField.setAttribute("value", message.data[key]);
form.appendChild(hiddenField);
}
document.body.appendChild(form);
form.submit();
}
chrome.runtime.onMessage.addListener(onMessageHandler);
background.js (or other non-content script inside the extension)
function postData(url, data) {
chrome.tabs.create(
{ url: chrome.runtime.getURL("post.html") },
function(tab) {
var handler = function(tabId, changeInfo) {
if(tabId === tab.id && changeInfo.status === "complete"){
chrome.tabs.onUpdated.removeListener(handler);
chrome.tabs.sendMessage(tabId, {url: url, data: data});
}
}
// in case we're faster than page load (usually):
chrome.tabs.onUpdated.addListener(handler);
// just in case we're too late with the listener:
chrome.tabs.sendMessage(tab.id, {url: url, data: data});
}
);
}
// Usage:
postData("http://httpbin.org/post", {"hello": "world", "lorem": "ipsum"});
Note the double messaging: with chrome.tabs.create callback we can't be sure that the listener is ready, nor can we be sure it's not done loading yet (though in my testing, it's always still loading). But better safe than sorry.
From #Amman Cheval's comments on the question:
[send] your dynamic data to the background file, creating a new tab which includes a form. Fill up the form using your form using the content using the background file, and then submit the form.
Read up about content scripts on Google's docs first. Then read up on message passing. After you've understood all of that, it'd be fairly simple to send a message from the script, to the background, and to the script of a different tab.

Categories