Error when loading an external URL in an iframe with Electron - iframe

I am trying to create a desktop application using Electron but I am unable to load an external URL like google.com in an iframe.
The code below, inside index.html, triggers an error.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
<!-- All of the Node.js APIs are available in this renderer process. -->
<iframe src="http://www.w3schools.com"></iframe>
<script>
// You can also require other files to run in this process
require('./renderer.js')
</script>
</body>
</html>
The error :
index.html:1 Refused to display 'https://www.w3schools.com/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.
www.w3schools.com/ Failed to load resource: net::ERR_BLOCKED_BY_RESPONSE
What is causing this issue and how can I resolve it?

Adding to what has already been answered by Sjoerd Dal.
Adding External URL using IFRAME : Sites block adding their web pages to any other web page, for avoiding click-jacking. This is usually done by :
a. Adding a response in the header. This stops pages which are not whitelisted/not from same-origin to be included in iframes
b. Checking if top window is same as current window.
Now to answer your question, there is actually a very easy way to do that:
const urls = [
"https://www.google.com"
]
const createWindow = () =>{
win = new BrowserWindow({
center: true,
resizable: true,
webPreferences:{
nodeIntegration: false,
show: false
}
});
win.maximize();
win.webContents.openDevTools();
//win.webContents.
console.log(urls[0]);
win.loadURL(urls[0]);
// win.loadURL(url.format({
// pathname: path.join(__dirname,"index.html"),
// protocol: 'file',
// slashes: true
// }));
win.once('ready-to-show',()=>{
win.show()
});
win.on('closed',()=>{
win = null;
});
}
app.on('ready', createWindow);

Most sites these days block other people from iframing them. As you can see with this error, the site only allows iframes coming from the same domain. As an alternative you can use Electron's webview tag which starts the website on a separate thread, sandboxed in its own BrowserWindow. https://electronjs.org/docs/api/webview-tag

Related

Dynamic Server-Side Rendering with Svelte. How to hydrate it on a client?

Assume we have a web page with the HTML lang attribute set dynamically based on the Accept-language header of the request. The body of the page is also language-specific. What I came up with:
require('svelte/register')
// Render the body
const Body = require('./Body.svelte').default
const body = Body.render({ language })
// Render the entire page
const Page = require('./Page.svelte').default
const page = Page.render(Object.assign(body, { language })
// Send page.html to the client
Where Page.svelte is:
<script>
export let html
export let language
</script>
<!DOCTYPE html>
<html lang="{ language }">
<body>
{ #html html }
</body>
</html>
Everything works fine. But now I'd like to have the page interact. I use the Body.svelte in the App and set hydrate option:
const app = new App({
target: document.body,
hydrate: true
})
Without this option set the content is rendered twice. With the option set we have an error in the browser:
Uncaught Error: options.hydrate only works if the component was compiled with the `hydratable: true` option
We can pass options to the compiler, according to the documentation:
require('svelte/register')({ hydratable: true })
It says:
hydratable: true adds a marker to each element in the <head> so that the component knows which elements it's responsible for removing during hydration
But I observe no markers. It has no effect on client-side rendering, and the error remains.
Am I missing something?

Google Analytics API OAuth after clicking "Allow" cannot connect to local host

I'm trying to obtain an access token for Google Analytics API.
After creating a project in the developers console and granting acess to the Analytics API I reached the "create credentials" step and created new credentials for a web application.
On these credentials I set the Javascript origins to http://localhost:8080 and also http://localhost:5000. Then I set authorized redirect URIs to http://localhost:8080/oauth2callback as well as http://localhost:5000/oauth2callback.
Then, when I attempt to authorize I'm asked to enter my clientId and secret, which I do, then new browser tab opens and I'm asked to choose an account and then after that select "Allow".
Then, when I click "Allow" I'm taken to this page:
I also tried creating credentials for an application type of "other" but the exact same thing happened.
I've found numerous posts on stack overflow about this but none of the answers were able to solve my problem. Not sure which other info to provide. I even tried clearing history and using different browsers but with no success.
How can I give my application authorization to Google Analytics using OAuth?
This issue has nothing to do with localhost or your redirect uris or JavaScript origins. The issue is that your code is not set up to handle the call back from the authentication server. It would have helped if you had posted your code so it will be hard to know what the problem might be.
You should check the official example here Hello analytics js tutorial
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello Analytics Reporting API V4</title>
<meta name="google-signin-client_id" content="<REPLACE_WITH_CLIENT_ID>">
<meta name="google-signin-scope" content="https://www.googleapis.com/auth/analytics.readonly">
</head>
<body>
<h1>Hello Analytics Reporting API V4</h1>
<!-- The Sign-in button. This will run `queryReports()` on success. -->
<p class="g-signin2" data-onsuccess="queryReports"></p>
<!-- The API response will be printed here. -->
<textarea cols="80" rows="20" id="query-output"></textarea>
<script>
// Replace with your view ID.
var VIEW_ID = '<REPLACE_WITH_VIEW_ID>';
// Query the API and print the results to the page.
function queryReports() {
gapi.client.request({
path: '/v4/reports:batchGet',
root: 'https://analyticsreporting.googleapis.com/',
method: 'POST',
body: {
reportRequests: [
{
viewId: VIEW_ID,
dateRanges: [
{
startDate: '7daysAgo',
endDate: 'today'
}
],
metrics: [
{
expression: 'ga:sessions'
}
]
}
]
}
}).then(displayResults, console.error.bind(console));
}
function displayResults(response) {
var formattedJson = JSON.stringify(response.result, null, 2);
document.getElementById('query-output').value = formattedJson;
}
</script>
<!-- Load the JavaScript API client and Sign-in library. -->
<script src="https://apis.google.com/js/client:platform.js"></script>
</body>
</html>

jsdom does not fetch scripts on local file system

This is how i construct it:
var fs = require("fs");
var jsdom = require("jsdom");
var htmlSource = fs.readFileSync("./test.html", "utf8");
var doc = jsdom.jsdom(htmlSource, {
features: {
FetchExternalResources : ['script'],
ProcessExternalResources : ['script'],
MutationEvents : '2.0'
},
parsingMode: "auto",
created: function (error, window) {
console.log(window.b); // always undefined
}
});
jsdom.jQueryify(doc.defaultView, 'https://code.jquery.com/jquery-2.1.3.min.js', function() {
console.log( doc.defaultView.b ); // undefined with local jquery in html
});
the html:
<!DOCTYPE HTML>
<html>
<head></head>
<body>
<script src="./js/lib/vendor/jquery.js"></script>
<!-- <script src="http://code.jquery.com/jquery.js"></script> -->
<script type="text/javascript">
var a = $("body"); // script crashes here
var b = "b";
</script>
</body>
</html>
As soon as i replace the jquery path in the html with a http source it works. The local path is perfectly relative to the working dir of the shell / actual node script. To be honest i don't even know why i need jQueryify, but without it the window never has jQuery and even with it, it still needs the http source inside the html document.
You're not telling jsdom where the base of your website lies. It has no idea how to resolve the (relative) path you give it (and tries to resolve from the default about:blank, which just doesn't work). This also the reason why it works with an absolute (http) URL, it doesn't need to know where to resolve from since it's absolute.
You'll need to provide the url option in your initialization to give it the base url (which should look like file:///path/to/your/file).
jQuerify just inserts a script tag with the path you give it - when you get the reference in the html working, you don't need it.
I found out. I'll mark Sebmasters answer as accepted because it solved one of two problems. The other cause was that I didn't properly wait for the load event, thus the code beyond the external scripts wasn't parsed yet.
What i needed to do was after the jsdom() call add a load listener to doc.defaultView.
The reason it worked when using jQuerify was simply because it created enough of a timeout for the embedded script to load.
I had the same issue when full relative path of the jquery library to the jQueryify function. and I solved this problem by providing the full path instead.
const jsdom = require('node-jsdom')
const jqueryPath = __dirname + '/node_modules/jquery/dist/jquery.js'
window = jsdom.jsdom().parentWindow
jsdom.jQueryify(window, jqueryPath, function() {
window.$('body').append('<div class="testing">Hello World, It works')
console.log(window.$('.testing').text())
})

Is it possible to load Mootools in a parent frame and then re-use it in iframes?

Example
-- begin: index.html --
<!doctype html>
<html>
<head>
<title>Index</title>
<script type="text/javascript" src="mootools.js"></script>
</head>
<body>
<iframe src="iframe.html" id="innerFrame">blah</iframe>
</body>
</html>
-- end: index.html --
-- begin: iframe.html --
<!doctype html>
<html>
<head>
<title>iFrame</title>
</head>
<body>
<form>
<input id="inputField" type="text" value="this is text." />
</form>
<script type="text/javascript">
$('inputField').set('value', 'updated text');
</script>
</body>
</html>
-- end: iframe.html --
Currently, $('inputField').set('value', 'updated text'); doesn't work :-\
Yes, assuming the iframe and it's parent window are on the same domain, it is possible to load the Mootools scripts once in the parent, and then programmatically extend the IFrame's window and document, instead of re-loading the script within the iframe. It is not the default behavior, as you've noticed, and probably for good reason - I'm guessing most people will tell you it's more trouble than it's worth.
In fact, the IFrame shortcut element constructor used to do that exact thing, but it was ultimately considered to be too much of a hack and not worth the effort to maintain as part of the framework long-term, so they dropped it - this why the documentation for IFrame is kind of odd ("IFrame Method: constructor, Creates an IFrame HTML Element and extends its window and document with MooTools.", and then right below after the example, "Notes: An IFrame's window and document will not be extended with MooTools methods.").
So, the most straightforward way to have $(..) useable in your iframe is just to have the iframe include the Mootools script. If you're feeling fancy, you could also have your parent window inject the Mootools script into the iframe's HEAD, for example:
index.html
<html>
<head>
<title>Parent</title>
<script type="text/javascript" src="mootools.js"></script>
</head>
<body>
<iframe id="innerFrame"></iframe>
<script type="text/javascript">
var mooFrame = new IFrame("innerFrame", {
src:"iframe.html",
events: {
load: function(){
var mooEl = new Element('script', {
type: 'text/javascript',
src: "mootools.js",
events: {
load: function(){
//passed to mooFrame by the iframe
this.pageReady();
}.bind(this)
}
});
this.contentDocument.head.appendChild(mooEl);
}
}
});
</script>
</body>
</html>
iframe.html
<html>
<head>
<title>Iframe</title>
</head>
<body>
<div id="iframe_element"></div>
<script type="text/javascript">
parent.mooFrame.pageReady = function(){
/* Put your iframe javascript in here */
$('iframe_element').set("text", "Fancy!");
};
</script>
</body>
</html>
Update (July 29th): I was fooling around with this idea again and realized there's a fairly obvious though pretty ham-fisted way to transfer Mootools functionality defined in the parent index.html window to the inner iframe: simply include the entire Mootools source into the parent window (remove the src attribute from the existing script element and add an id), and copy that newly enormous element's text into the new script node that gets injected into the head of the iframe. Inlining the Mootools code in the script element in this fashion gives you access to the contents of the element, which you don't get when the javascript is loaded from an external file via the src attribute.
Of course, this..concept is only relevant if the parent window and iframe are on the same-domain, (same as the code provided above).
The obvious drawback is that the Mootools source isn't cached. I'm not sure if there's a use-case where this method would be more optimal than just including mootools in both parent and iframe. In any event, change the index.html file to this:
<html>
<head>
<title>Parent</title>
<script type="text/javascript" id="mootools_js">
**COPY-PASTE THE CONTENTS OF mootools-core.js HERE**
</script>
</head>
<body>
<iframe id="innerFrame"></iframe>
<script type="text/javascript">
var mooFrame = new IFrame("innerFrame", {
src:"iframe.html",
events: {
load: function(){
var mooEl = new Element('script', {
id: 'mootools_iframe_core',
type: 'text/javascript',
html: $('mootools_js').innerHTML
});
this.contentDocument.head.appendChild(mooEl);
this.pageReady();
}
}
});
</script>
</body>
</html>
My previous answer offered two alternative ways of doing the task in question ("load Mootools in a parent frame and then re-use it in iframes"). The first method didn't "re-use" the Mootools functionality loaded into the parent frame, but was rather an alternative way to load the script in the inner iframe. The second method was just a hacky way of copying over the script by putting the entire mootools core source inline in a script element and then copying that element's content into a script element in the iframe's head (hardly optimal).
This following method does programatically extend the window and document objects of the inner iframe. Again, it is assumed that both the parent page and the iframe are on the same domain.
In my (brief and simple) testing, loading the source in both parent and iframe resulted in 72.1 KB transferred at around 130ms (to finish loading both the parent and iframe pages), while the page that loaded the source and then extended the iframe was 36.8 KB and took around 85ms to load both parent and iframe. (that's with gzip on the server...file size of uncompressed/unminified core source is around 134 kb).
For this method a few trivial additions/edits are made to the mootools core source. Download an uncompressed version of mootools-core-1.3.2.js, and rename it to 'mootools-core-init.js' (or whatever). The following steps assume that you checked all boxes on the core builder page except 'Include Compatibility'.
Add this to the top of the 'mootools-core-init.js' file (above the first self-calling anonymous function):
var initMootoolsCore = function(){
var window = this;
var document = this.document;
Add this to the very bottom of the core js file:
};
initMootoolsCore.call(window);
Do the following find/replace tasks:
1
Find:})();
Replace: }).call(this);
2
Find: if (Browser.Element) Element.prototype = Browser.Element.prototype;
Replace: if (this.Browser.Element) Element.prototype = this.Browser.Element.prototype;
3
Find: var IFrame = new Type
Replace: var IFrame = this.IFrame = new Type
4
Find: var Cookie = new Class
Replace: var Cookie = this.Cookie = new Class
(download | compressed version)
In your parent index.html file, put the following script element in the head
<script type="text/javascript" src="mootools-core-init.js"></script>
Finally, in your iframe.html file, put the following inline script element in the head to extend the iframe's window and document (it must be before any included or inline scripts that need to use Mootools):
<script type="text/javascript">parent.initMootoolsCore.call(window);</script>
No, the iframe.html is an independent page. It does not "inherit" anything from the previous page.

How to Force the page to be loaded only within an IFrame?

I have a set of asp.net pages which I wish they should only be accessible or loaded when they are loaded from an IFrame. If an attempt is made to access the pages directly via browser address bar then the page should not be displayed at all or display the message to the user.
I tried using cookies and sesions, but they are not that effective becuase once the cookie/session is created you can access the pages directly from browser, bypassing IFrame.
My development platform is asp.net 2.0+, vs2008, C# 2.0+
This is an example of one of the few times it is better to put the script in the head tag.
<html>
<head>
<title>sandBox</title>
<script type="text/javascript">
if (frameElement == null) {
//change location or close
window.location = "http://stackoverflow.com";
// or window.close();
}
</script>
</head>
<body>
content goes here
</body>
</html>
Try this inside your head tag:
<script>
if(window.self !== window.top); //inside an iframe
else window.location = "http://stackoverflow.com/" // Outside
</script>
Use this JS in the page to check whether it is in iframe or not.
if(window == window.top) {
//page is not in an iframe
}

Resources