Loading local content through XHR in a Chrome packaged app - ajax

I'm trying to load in a web app that I've built using Backbone and it pulls in JSON and HTML template files that are stored locally. I was wondering with Chrome packaged apps whether it's possible to load these files by using some sort of 'get'/ajax request?
Currently I'm getting this...
OPTIONS chrome-extension://fibpcbellfjkmapljkjdlpgencmekhco/templates/templates.html Cannot make any requests from null. jquery.min.js:2
XMLHttpRequest cannot load chrome-extension://fibpcbellfjkmapljkjdlpgencmekhco/templates/templates.html. Cannot make any requests from null.
I can't find any real information on how to do this so any help would be great thanks!

Yes, it's totally possible, and it's easy. Here's a working sample. Try starting with this, confirm that it works, and then add back in your own code. If you hit a roadblock and come up with a more specific question than whether XHRs work in packaged apps, you might want to ask a new question.
manifest.json:
{
"name": "SO 15977151 for EggCup",
"description": "Demonstrates local XHR",
"manifest_version" : 2,
"version" : "0.1",
"app" : {
"background" : {
"scripts" : ["background.js"]
}
},
"permissions" : []
}
background.js:
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create("window.html",
{ bounds: { width: 600, height: 400 }});
});
window.html:
<html>
<body>
<div>The content is "<span id="content"/>"</div>
<script src="main.js"></script>
</body>
</html>
main.js:
function requestListener() {
document.querySelector("#content").innerHTML = this.responseText;
};
onload = function() {
var request = new XMLHttpRequest();
request.onload = requestListener;
request.open("GET", "content.txt", true);
request.send();
};
content.txt:
Hello, world!

You are making a request from a sandboxed page, and sandboxed pages have a null origin.
I have posted this issue question on the Google Group.
Unless Chrome decides to changed the sandbox policy, it appears the only workaround is to make XHR requests from a non-sandboxed page and use Chrome's message passing API to give it to your sandboxed page.
I don't know why it has to be so difficult.
EDIT:
The answer from the Chrome Team was to change the CORS header to *.

I believe your problem is on the server side, rather than the client side. The server needs to send the following header for jQuery to deal with the response:
Access-Control-Allow-Origin: *
The problem, with this, however, is that any page can load that content now. Once you know the ID of your extension, you can change that header to something like:
Access-Control-Allow-Origin: chrome-extension://gmelhokjkebpmoejhcelmnopijabmobf/
A short test of something like the following showed these to work:
<h1>Content Below</h1>
<div id="loadme"></div>
<script src="jquery-1.9.1.min.js"></script>
<script src="app.js"></script>
// app.js
$(document).ready(function() {
$.get('http://localhost:8080/content.php', function(data) {
$('#loadme').html(data);
});
});
This would fail with the following message if I didn't add the Access-Control-Allow-Origin header:
XMLHttpRequest cannot load http://localhost:8080/newhope/deleteme.php.
Origin chrome-extension://gmelhokjkebpmoejhcelgkfeijabmobf is not allowed by
Access-Control-Allow-Origin.
Once I added the Access-Control-Allow-Origin header on the php response, it worked fine.
Again, setting this to * may be a security risk as any browser page anywhere is allowed to load it inline.

Related

I can't seem to generate content for dynamic content load

I am using wordpress and php along with ajax to create a random loading of customer reviews on our main page
function loadContent() {
$.ajax({
type: "GET",
url: 'http://skillsetsonline.ssosv.com/contentLoader.php',
data: {
company: 1
},
success: function(data) {
alert(data);
var currReview = document.getElementById('reviewRand');
currReview.innerHTML = data;
}
});
}
setTimeout(loadContent, 10000); // milliseconds, so 10 seconds = 10000ms
<div id="reviewRand" class="elementToFadeInAndOut" style="font-color:#FFF;">Hi how are you</div>
I pasted the ajax command in from a stackoverflow posting that was an accepted answer but may not have it exactly right this does not include the fading CSS code I use but that is working I just need to change the content.
Currently "Hi how are you" fades in every 10 seconds. One thing I have not learned about yet with this ajax command is the
data:{company:1}
I know it simply passes &company=1 to the GET URL but in my case I do not need to send anything and since it should not break anything if it is sent I left it alone not sure if
data:{}
would work and be cleaner
I have verified that the url used does get a random review
formatted like this
I love this program.blah blah.<br>
A USER<br>
A location<br>
June 2016<br>
Each line is formatted in CSS via a class tag
Any ideas would be greatly appreciated
Since the domain you're making the AJAX request to is on a different domain/origin, what you're running in to is a CORS issue. By default, the client will not allow you to update the page with data from an AJAX request that is served on a different origin than the site where the request originated. You can read about making CORS changes here https://enable-cors.org/
A common way around this is to serve the response via JSONP. You can do this in your script at http://skillsetsonline.ssosv.com/contentLoader.php if you have access to change that file. There are also third-party sites that will request that URL for you and create a proxy that serves the response via JSONP, then you can use it on your website.
Here's an example utilizing a JSONP proxy on https://crossorigin.me
function loadContent() {
$.ajax({
type: "GET",
url: 'https://crossorigin.me/http://skillsetsonline.ssosv.com/contentLoader.php',
success: function(data) {
var currReview = document.getElementById('reviewRand');
currReview.classList.add('ready');
currReview.innerHTML = data;
}
});
}
setTimeout(loadContent, 0); /* changed this for the demo */
#reviewRand:not(.ready) {
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="reviewRand"><img src="http://thinkfuture.com/wp-content/uploads/2013/10/loading_spinner.gif"></div>

API Call From Word Web Add-In (Office.Js) Is Not Working: CORS Issue?

Friends,
I am trying to call API from Word Add-in and getting "Access Denied" error. I did some research and it looks like "Cross Origin Resource Sharing" is the cause.
1. Web API
I am hosting Web API 2 locally at "http://localhost:61546/api/ORG_NAMES"
& I have enabled CORS to accept all origins, See below WebApiConfig.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
2. Test Application
To test this API to ensure it supports CORS, I have created below page and hosted on localhost:52799/home.html, I was able to get expected response. I have tested this in IE 10 & Chrome.
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>
$(document).ready(function(){
$("button").click(function () {
var obj;
.support.cors = true;
$.getJSON("http://localhost:61546/api/ORG_NAMES/112233",
function (data) {
alert(data.ORG_ID);
});
});
});
</script>
</head>
<body>
<button>Click me</button>
</body>
3. Word Add-In
Now I wanted to call this API from my Word Web Add-In. Word Add-In running from different host https://localhost:44339/, see below code. Here getJSON returns "Access Denied".
var OrgID;
$.getJSON("http://localhost:61546/api/ORG_NAMES/112233",
function (data) {
OrgID = data.ORG_ID;
});
Also when I call API from word add-in, it's not going to fiddler.
Note: This is "Web Add-ins --> Word Add-in" project.
4. Fix - Need Help
Not sure why I am getting "Access Denied" error from Word-Add-In, if CORS is the issue then my test application (#2) shouldn't have worked, correct ?
I have tried call JSON using "$.ajax", "XMLHttpRequest" but it didn't work.I might be missing some configuration settings.
Appreciate any help here.
Let me know if you need more information.
Since it sounds like an issue within an Office Add-in only, rather than in a regular page, have you tried setting your AppDomains in the manifest file? See "Specify domains you want to open in the add-in window" in https://dev.office.com/docs/add-ins/overview/add-in-manifests
<?xml version="1.0" encoding="UTF-8"?>
<OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="TaskPaneApp">
<Id>c6890c26-5bbb-40ed-a321-37f07909a2f0</Id>
<Version>1.0</Version>
<ProviderName>Contoso, Ltd</ProviderName>
<DefaultLocale>en-US</DefaultLocale>
<DisplayName DefaultValue="Northwind Traders Excel" />
<Description DefaultValue="Search Northwind Traders data from Excel"/>
<AppDomains>
<AppDomain>https://www.northwindtraders.com</AppDomain>
</AppDomains>
<DefaultSettings>
<SourceLocation DefaultValue="https://www.contoso.com/search_app/Default.aspx" />
</DefaultSettings>
<Permissions>ReadWriteDocument</Permissions>
</OfficeApp>
You will not need Jsonp if you are making Ajax calls. You will have to make sure that you all launches with HTTPS, if it is launching in HTTP it will block that traffic. Remember that office-js back bone is IE and there for; for security purposes the api will only allow HTTPS
Update
Remember that an office-js add in is actually two projects and you must make sure your projects are both launching in HTTPS. Also I would just look over the Manifest file and look at your source and make sure that is point at HTTPS
I had same issue using ajax could not call web-api.NET MVC.
Web api side(Server side):
Implement CORS in Web api because excel office.js works on diffent port and binds proxy object of server inside excel while web api are held on another port so it is as good as having 2 different domains on local so browser automatically blocks request made.
So Cross origin Resource sharing is required.
Enable Https for web apis.
http://csharp-video-tutorials.blogspot.com/2016/09/aspnet-web-api-enable-https.html
Client side
Just make call using ajax as shown below.
url: 'https://localhost:44319/api/Default/PostItems'
Note : https : is compulsory required .
function makeAjaxCall(rangeJSON) {
$.ajax({
url: 'https://localhost:44319/api/Default/PostItems',
type: 'POST',
data: rangeJSON,
contentType: 'application/json;charset=utf-8',
}).done(function (data) {
console.log(data)
app.showNotification(data.Status, data.Message);
}).fail(function (status) {
app.showNotification('Error', 'Could not communicate with the server.');
}).always(showResponse);
}
function exceltojson() {
Excel.run(function (ctx) {
var range = ctx.workbook.worksheets.getItem("Sheet1").getRange("A1:BO765");
range.load("values, numberFormat");
ctx.sync().then(
function () {
makeAjaxCall(JSON.stringify(range.values));
}).catch(function (error) {
console.log(error);
});
});
function showResponse(object) {
console.log(object);
$("#output").text(JSON.stringify(object,null, 4));
}

API request from front-end using sails.io.js

I have a basic front-end (html, css, jquery) and I'd like to use sails.io.js to communicate with an API server (developped with sails, with cors enabled). The API is running on localhost:10000 but it will be on an another domain than the one of the webclient later on.
Directly from jquery, I can issue some get request to this API and get the expected results.
When it comes to websocket, I have some problems...
In the index.html (just to test), I put the following:
<script src="js/sails.io.js"></script>
<script type="text/javascript">
io.sails.url('http://localhost:10000');
io.socket.get('/data', function serverResponded (body, sailsResponseObject) {
// body === sailsResponseObject.body
console.log('Sails responded with: ', body);
console.log('with headers: ', sailsResponseObject.headers);
console.log('and with status code: ', sailsResponseObject.statusCode);
});
</script>
But Chrome's developer tools tell me
ReferenceError: io is not defined
Any idea ?
UPDATE
I'm serving index.html with a web server (python -m SimpleHTTPServer)
I've installed sails.io.js using bower.
I've try to make this test as simple as possible:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<script src="bower_components/sails.io.js/dist/sails.io.js"></script>
<script src="index.js"></script>
</body>
</html>
index.js:
window.onload=function(){
io.sails.url = 'http://localhost:10000';
io.socket.get('http://localhost:10000/data', function (body, response) {
console.log('Sails responded with: ', body);
});
};
My sails (0.9.16) API is only returning a json object on the GET /data route.
I have implemented a dummy __getcookie function in the api:
'get /__getcookie': function(req, res, next){
res.json({ok: 123});
}
And commented the line 481 in interpret.js (Scott comments below).
I have also modify config/socket.js with:
authorization: false,
=> I can now get the result from the /data route of my API :)
But... on each request I have the following error:
error: Error: No valid session available from this socket.
First of all, sails.io.js includes the code for socket.io.js, so there is no need to try and include that separately. You should remove this line:
<script src="bower_components/socket.io/lib/socket.js"></script>
Next, if you're just loading index.html from disk (rather than serving it from a web server), you'll need to tell the Sails socket client what URL to connect to:
io.sails.url = 'http://localhost:10000';
Put this anywhere before you start making socket calls; the library is smart enough to wait until its connected before trying to make the calls. So, altogether:
window.onload=function(){
io.sails.url = 'http://localhost:10000';
io.socket.get('http://localhost:10000/data', function (body, sailsResponseObject) {
console.log('Sails responded with: ', body);
console.log('with headers: ', sailsResponseObject.headers);
console.log('and with status code: ', sailsResponseObject.statusCode);
});
};
should work. You should be able to see in the console whether or not the socket connected by looking for the "io.socket connected successfully." message.
did you try with a / in front of the src, like:
< script src="js/sails.io.js">
Do you have the sails.io.js in the /assets/js/ folder (sails 0.10) or in the /assets/linker/js folder (sails 0.9 and below).
Did sails lift copied that js file to .tmp/public/js folder?
Where is your index.html file located?

Meteor.http.call Client-side Results Undefined

Consuming Mashape Airbnb API:
The following sits inside the Clients->airbnb.js file.
My Results are undefined. But using the same API, http://jsfiddle.net/ismaelc/FZ5vG/
works just fine.
function getListings(place) {
alert(place);
Meteor.http.call("GET", "https://airbnb.p.mashape.com/s",
{params : {location:place},
headers : {"X-Mashape-Authorization":"ffnGO1suGtJEjqgz4n7ykeuCbDP1hexv"}},
function (error, result) {
$('#listings').html(EJSON.stringify(result.data));
console.log("Status: "+result.statusCode);
console.log("Content: "+result.statusCode);
console.log("data: "+EJSON.stringify(result.data));
console.log("error: "+error.message);
}
);
}
Template.where.events ({
'click #find': function(event){
var place = $('#location').val();
getListings(place);
}
});
My Google Chrome Web Developers Tool is giving me odd HTTP Response.
IMG: Here http://imgur.com/f5u2C7X
Also, I momentarily see my console.log and then it just disappears. Why is this?
You can use the network tab in the chrome dev kit, make sure its already open before you do the request and it should just add on and you can view its text content to find where its all going wrong.
The response tab should have the text it gets back:
Of note is just check your api you might need to use params (HTTP POST params) instead of data (JSON post in the body), e.g {params : {location:place}.

Extjs 4 (with a code for 3.4 below) downloading a file returned from a post request

I have seen questions slightly related to this, but none that answer my problem. I have set up an Ext.Ajax.request as follows:
var paramsStringVar = 'param1=1&param2=two&param3=something&param4=etc';
Ext.Ajax.request({
url: '/cgi-bin/url.pl',
method:'POST',
params:paramsStringVar,
timeout:120000,
success: function(response, opts){
var objhtml = response.responseText; //content returned from server side
console.log(objhtml);
}
});
This request retrieves the appropriate content from the backend. One parameter is outputType, which can take values {html, excel, csv}. When returning html to display I am able to handle and display it correctly. Now on to the problem...
When I set the outputType parameter to csv or excel, I get back the appropriate content as csv or tsv(excel) as requested. BUT, I don't want the content, I want a prompt to download the file(csv or excel). How can I have the browser auto prompt the user to download the file instead of just retrieving the text content within extjs?
Version 4.07 so I can't use any 4.1 only features
There seems to be no bulletproof solution but there are several approaches I would try:
1) Use an iframe instead of real XHR to POST data to the server, e.g. <form action="/something" target="myiframe"> where myiframe is the name of your hidden iframe. That way your form would use the iframe (not your main window) to submit data to the configured URL. Your server should set response header as application/octet-stream (or some ither MIME type for binary data) so the browser triggers download. Otherwise (if html returned in your case) you can just retrieve iframe's body innerHTML and display it to the user in UI. While using an iframe (or a new window) instead of XHR doesn't sound like the best idea, this solution seems to be the most reliable so far (and with best browser support).
Here is a slightly modified example from Ext.form.Basic docs page:
Ext.create('Ext.form.Panel', {
title: 'Basic Form',
renderTo: Ext.getBody(),
width: 350,
// Any configuration items here will be automatically passed along to
// the Ext.form.Basic instance when it gets created.
// *THIS* makes the form use a standard submit mechanism, not XHR
/**/standardSubmit: true,
// URL to submit to
url: 'save-form.php',
items: [{
fieldLabel: 'Field',
xtype: 'textfield',
name: 'theField'
}],
buttons: [{
text: 'Submit',
handler: function() {
// The getForm() method returns the Ext.form.Basic instance:
var form = this.up('form').getForm();
if (form.isValid()) {
// Submit the Ajax request and handle the response
form.submit({
success: function(form, action) {
Ext.Msg.alert('Success', action.result.msg);
},
failure: function(form, action) {
Ext.Msg.alert('Failed', action.result.msg);
},
// You can put the name of your iframe here instead of _blank
// this parameter makes its way to Ext.form.Basic.doAction()
// and further leads to creation of StandardSubmit action instance
/**/ target: '_blank'
});
}
}
}]
});
There are two key parameters here (lines marked with /**/):
standardSubmit: true config that you pass to your form will make it do a standard submit instead of XHR.
Passing a target parameter to the form's submit action. This feature is not documented but you can see it being used in Ext.form.action.Submit source code (all options that you pass to Ext.form.Basic.submit() method end up as parameters of Ext.form.action.* instance.
In the example code I put target: '_blank' to demonstrate that it works right away (will create a new browser window). You can replace it with the name of your iframe later but I suggest that you first test how your form submits data to a regular new window and then develop logic that creates and processes an iframe. You will have to process the result inside iframe yourself, thought. It's not that difficult, see Ext.data.Connection.upload() implementation as an example of iframe processing.
ExtJS actually already uses the iframe technique for file uploads. See Ext.data.Connection and Ext.form.field.Field.isFileUpload() for an idea of how it can work.
2) Suggested here: Using HTML5/Javascript to generate and save a file.
If you don't want to go the iframe way, you can try generate data URI from response data and navigate to that URI triggering download:
content = "Hello world!";
uriContent = "data:application/octet-stream," + encodeURIComponent(content);
window.location.href = uriContent;
Again, mimetype is essential here. This worked for me, you should note, however, that browsers impose a size limit to data URIs (256Kb is a safe bet).
3) Another answer in the mentioned thread links to FileSaver.js library the implements the (abandoned?) w3 spec. Usage and demo here. It uses [BlobBuilder] to generate a blob of binary data that is further used to initialize downloads using one of several methods. While this solution seems to work, it uses deprecated APIs and may not be future-proof.
Below is my solution. This is how I have it currently working. The response generates a download/open prompt, based on a response type of text/csv. Note that no iFrame or reference to an iframe are needed. I spent a lot of time hung up on the need for an iFrame, which actually broke my solution. An iFrame is not needed to generate a download prompt. What is needed is a request(submittal) similar to this one, along with a backend generating the appropriate csv with text/csv response header.
var hiddenForm = Ext.create('Ext.form.Panel', {
title:'hiddenForm',
standardSubmit: true,
url: /cgi-bin/url.pl
timeout: 120000,
height:0,
width: 0,
hidden:true,
items:[
{xtype:'hiddenField', name:'field1', value:'field1Value'},
// additional fields
]
})
hiddenForm.getForm().submit()
The standardSubmit line is vital
You don't need to create a form panel and make it hidden in your extjs file. We can add a html form and on click of button in extjs file we can submit the form using the url. This will work both in IE as well as chrome browsers. Below is my code i tried and its working fine,
<form action="<%=fullURL%>/DownloadServlet.do" method="get" id="downloadForm" name="downloadForm" target="_self">
</form>
click:
{
fn: function()
{
document.getElementById('downloadForm').submit();
}
}
To get it working on ExtJS 3.4:
var hiddenForm = new Ext.FormPanel({
id:'hiddenForm',
region: 'south',
method: 'POST',
url: "/cgi/test.wsgi",
height: 0,
standardSubmit: true,
hidden:true,
items:[
{xtype:'hidden', name:'p', value:p},
{xtype:'hidden', name:'g', value:g},
// ...
],
});
linkThis = new Ext.Button({
text: 'Download this CSV',
handler: function() {
hiddenForm.getForm().submit();
},
maxHeight: 30,
});
Remember that in order to make it working, you should put the hiddenForm in any container (i.e. in the same Ext.Window of the button), for example:
risultatiWindow = new Ext.Window({
title: 'CSV Export',
height: 400,
width: 500,
....
items: [...., hiddenForm]
});

Resources