NW.js - webview - Angular is not defined error - angularjs

I want control external website using NW.js and webview, but anytime I try this I have error - Angular is not defined.
My source:
index.html
html...head...body...
<webview id="webview" name="webview" src="https://google.com/" allownw></webview>
<script src="../js/script.js"></script>
...body...html
script.js
(function() {
var gui = require('nw.gui');
var win = gui.Window.get();
var webview = document.getElementById("webview");
var tray = new gui.Tray({
icon : 'assets/icon.png'
});
var menu = new gui.Menu();
menu.append(new gui.MenuItem({
type: 'normal',
label: '▶️ Play',
click: function() {
webview.executeScript({code:"var player=angular.element(document.body).injector().get('player'); player.play();"});
}
}));
tray.menu = menu;
}());
This code produce for me error: ReferenceError: angular is not defined
Note: Website google.com is only example.

From the nw.js docs:
loading local files in webview
Add the following permission to the manifest:
"webview": {
"partitions": [
{
"name": "trusted",
"accessible_resources": [ "<all_urls>" ]
}
]
}
and add 'partition="trusted"' attribute to the webview tag.
This is one reason why the <script tag cannot access your ../js/script.js file. I also suggest using a non-relative path to your local file example: chrome-extension://yourdomain/js/script.js (and that it be relative to the root not 'up' a directory).
From google's docs, you can set accessible_resources to be specific patterns or files.
The insertCss, setUserAgentOverride, permissionrequest event, and especially executeScript from google's documentation will likely be of interest to you.
NOTE: you may not want to give that webview access to your nwjs context (allownw) because it can access anything that your program has access to. This is especially true of both an external URL and http where MITM tampering or a third-party change could potentially cause someone to gain elevated access to the PC your application is running on.
For your example, I would keep the script.js outside of the webview and have it instrument the webview via script injection.

Related

Blocked a frame with origin "https://example.com" from accessing a frame with origin "https://www.herokucdn.com". Protocols, domains, and ports [duplicate]

I am loading an <iframe> in my HTML page and trying to access the elements within it using JavaScript, but when I try to execute my code, I get the following error:
SecurityError: Blocked a frame with origin "http://www.example.com" from accessing a cross-origin frame.
How can I access the elements in the frame?
I am using this code for testing, but in vain:
$(document).ready(function() {
var iframeWindow = document.getElementById("my-iframe-id").contentWindow;
iframeWindow.addEventListener("load", function() {
var doc = iframe.contentDocument || iframe.contentWindow.document;
var target = doc.getElementById("my-target-id");
target.innerHTML = "Found it!";
});
});
Same-origin policy
You can't access an <iframe> with different origin using JavaScript, it would be a huge security flaw if you could do it. For the same-origin policy browsers block scripts trying to access a frame with a different origin.
Origin is considered different if at least one of the following parts of the address isn't maintained:
protocol://hostname:port/...
Protocol, hostname and port must be the same of your domain if you want to access a frame.
NOTE: Internet Explorer is known to not strictly follow this rule, see here for details.
Examples
Here's what would happen trying to access the following URLs from http://www.example.com/home/index.html
URL RESULT
http://www.example.com/home/other.html -> Success
http://www.example.com/dir/inner/another.php -> Success
http://www.example.com:80 -> Success (default port for HTTP)
http://www.example.com:2251 -> Failure: different port
http://data.example.com/dir/other.html -> Failure: different hostname
https://www.example.com/home/index.html:80 -> Failure: different protocol
ftp://www.example.com:21 -> Failure: different protocol & port
https://google.com/search?q=james+bond -> Failure: different protocol, port & hostname
Workaround
Even though same-origin policy blocks scripts from accessing the content of sites with a different origin, if you own both the pages, you can work around this problem using window.postMessage and its relative message event to send messages between the two pages, like this:
In your main page:
const frame = document.getElementById('your-frame-id');
frame.contentWindow.postMessage(/*any variable or object here*/, 'https://your-second-site.example');
The second argument to postMessage() can be '*' to indicate no preference about the origin of the destination. A target origin should always be provided when possible, to avoid disclosing the data you send to any other site.
In your <iframe> (contained in the main page):
window.addEventListener('message', event => {
// IMPORTANT: check the origin of the data!
if (event.origin === 'https://your-first-site.example') {
// The data was sent from your site.
// Data sent with postMessage is stored in event.data:
console.log(event.data);
} else {
// The data was NOT sent from your site!
// Be careful! Do not use it. This else branch is
// here just for clarity, you usually shouldn't need it.
return;
}
});
This method can be applied in both directions, creating a listener in the main page too, and receiving responses from the frame. The same logic can also be implemented in pop-ups and basically any new window generated by the main page (e.g. using window.open()) as well, without any difference.
Disabling same-origin policy in your browser
There already are some good answers about this topic (I just found them googling), so, for the browsers where this is possible, I'll link the relative answer. However, please remember that disabling the same-origin policy will only affect your browser. Also, running a browser with same-origin security settings disabled grants any website access to cross-origin resources, so it's very unsafe and should NEVER be done if you do not know exactly what you are doing (e.g. development purposes).
Google Chrome
Mozilla Firefox
Safari
Opera: same as Chrome
Microsoft Edge: same as Chrome
Brave: same as Chrome
Microsoft Edge (old non-Chromium version): not possible
Microsoft Internet Explorer
Complementing Marco Bonelli's answer: the best current way of interacting between frames/iframes is using window.postMessage, supported by all browsers
Check the domain's web server for http://www.example.com configuration for X-Frame-Options
It is a security feature designed to prevent clickJacking attacks,
How Does clickJacking work?
The evil page looks exactly like the victim page.
Then it tricked users to enter their username and password.
Technically the evil has an iframe with the source to the victim page.
<html>
<iframe src='victim-domain.example'/>
<input id="username" type="text" style="display: none;"/>
<input id="password" type="text" style="display: none;"/>
<script>
//some JS code that click jacking the user username and input from inside the iframe...
<script/>
<html>
How the security feature work
If you want to prevent web server request to be rendered within an iframe add the x-frame-options
X-Frame-Options DENY
The options are:
SAMEORIGIN: allow only to my own domain render my HTML inside an iframe.
DENY: do not allow my HTML to be rendered inside any iframe
ALLOW-FROM https://example.com/: allow specific domain to render my HTML inside an iframe
This is IIS config example:
<httpProtocol>
<customHeaders>
<add name="X-Frame-Options" value="SAMEORIGIN" />
</customHeaders>
</httpProtocol>
The solution to the question
If the web server activated the security feature it may cause a client-side SecurityError as it should.
For me i wanted to implement a 2-way handshake, meaning:
- the parent window will load faster then the iframe
- the iframe should talk to the parent window as soon as its ready
- the parent is ready to receive the iframe message and replay
this code is used to set white label in the iframe using [CSS custom property]
code:
iframe
$(function() {
window.onload = function() {
// create listener
function receiveMessage(e) {
document.documentElement.style.setProperty('--header_bg', e.data.wl.header_bg);
document.documentElement.style.setProperty('--header_text', e.data.wl.header_text);
document.documentElement.style.setProperty('--button_bg', e.data.wl.button_bg);
//alert(e.data.data.header_bg);
}
window.addEventListener('message', receiveMessage);
// call parent
parent.postMessage("GetWhiteLabel","*");
}
});
parent
$(function() {
// create listener
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
eventer(messageEvent, function (e) {
// replay to child (iframe)
document.getElementById('wrapper-iframe').contentWindow.postMessage(
{
event_id: 'white_label_message',
wl: {
header_bg: $('#Header').css('background-color'),
header_text: $('#Header .HoverMenu a').css('color'),
button_bg: $('#Header .HoverMenu a').css('background-color')
}
},
'*'
);
}, false);
});
naturally you can limit the origins and the text, this is easy-to-work-with code
i found this examlpe to be helpful:
[Cross-Domain Messaging With postMessage]
There is a workaround, actually, for specific scenarios.
If you have two processes running on the same domain but different ports, the two Windows can interact without limitations. (i.e. localhost:3000 & localhost:2000). To make this work, each window needs to change their domain to the shared origin:
document.domain = 'localhost'
This also works in the scenario that you are working with different subdomains on the same second-level domain, i.e. you are on john.site.example trying to access peter.site.example or just site.example
document.domain = 'site.example'
By explicitily setting document.domain; the browser will ignore the hostname difference and the Windows can be treated as coming from the 'same-origin'. Now, in a parent window, you can reach into the iframe: frame.contentWindow.document.body.classList.add('happyDev')
If you have control over the content of the iframe - that is, if it is merely loaded in a cross-origin setup such as on Amazon Mechanical Turk - you can circumvent this problem with the <body onload='my_func(my_arg)'> attribute for the inner html.
For example, for the inner html, use the this html parameter (yes - this is defined and it refers to the parent window of the inner body element):
<body onload='changeForm(this)'>
In the inner html :
function changeForm(window) {
console.log('inner window loaded: do whatever you want with the inner html');
window.document.getElementById('mturk_form').style.display = 'none';
</script>
I experienced this error when trying to embed an iframe and then opening the site with Brave. The error went away when I changed to "Shields Down" for the site in question. Obviously, this is not a full solution, since anyone else visiting the site with Brave will run into the same issue. To actually resolve it I would need to do one of the other things listed on this page. But at least I now know where the problem lies.
I would like to add Java Spring specific configuration that can effect on this.
In Web site or Gateway application there is a contentSecurityPolicy setting
in Spring you can find implementation of WebSecurityConfigurerAdapter sub class
contentSecurityPolicy("
script-src 'self' [URLDomain]/scripts ;
style-src 'self' [URLDomain]/styles;
frame-src 'self' [URLDomain]/frameUrl...
...
.referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
Browser will be blocked if you have not define safe external contenet here.
Open the start menu
Type windows+R or open "Run
Execute the following command.
chrome.exe --user-data-dir="C://Chrome dev session" --disable-web-security

Angular, without a browser?

Sometimes I want to experiment (in a local Node script) with some aspect of Angular - e.g. Services, DI, etc - stuff that has nothing to do with the Browser or the DOM. Is there a way to do that? i.e. load some base portion of the Angular Infrastructure? If I just require("angular") in a Node script, it complains:
ReferenceError: window is not defined
which makes sense because Angular lives for the Browser-window.
But it seems like some portions of Angular could be used for non-web applications - although that's not my reason for asking this. I'm just trying to improve my understanding Angular and sometimes want to do a little experiment while stripping away/ignore as much as possible.
Experimenting with Angular is best done in a browser, due to window and other API's Angular relies on.
However, if you're dead set on using Angular with node, you might look into the vm module which essentially lets you eval code with a specific stand-in object as a sort of proxy global object. e.g.:
const vm = require('vm');
const fs = require('fs');
const test = fs.readFileSync('./test.js', 'utf-8');
const windowProxy = {
document: {
createElement: function() {
return {
setAttribute: function() {},
pathname: ''
}
},
querySelector: function() {},
addEventListener: function() {}
},
location: {
href: ''
},
addEventListener: function() {}
};
windowProxy.window = windowProxy;
vm.createContext(windowProxy);
vm.runInContext(test, windowProxy);
will at least let you load Angular without complaining. Undoubtedly you would encounter more errors, and would have to polyfill the missing browser API's yourself.
You might also look into PhantomJS for a more robust testing environment, though that would no longer be Node.

AngularJS: Remove development section when running in production

I'm developing an AngularJS application inside Visual Studio 2013. I've added a custom HTML template with some debugging form fields to manipulate my scope when developing the site:
<div data-ng-include="'App/Development/development.html'"></div>
This is fixed in the bottom left corner and make it easier for me to quickly do some actions when testing the site.
But this should be removed when I deploy my website (Release configuration).
Does anyone have a good solution for doing this? Currently my App config and AngularJS is not integrated, so there is no way to tell my Angular application that it is running in production mode.
My solution was to just check the hostname, if localhost I'm showing my development bar.
app.controller('development', function ($scope) {
$scope.debug = document.location.hostname == "localhost";
});
Put an ng-clock on it so the div will not show before angular loads and an ng-if so you will not load ("include") needed sources for a regular user. have the service/controller check the host(property of the window object). if it's local, show the panel using ng-if. you can also have it check the query string. so you can debug on remote by affixing ?debug=true to the Url for example.
Lets assume that you are using mvc with razor
Create a cs file
public class ConfigManager
{
public static bool IsDebugMode()
{
#if DEBUG
return true;
#else
return false;
#endif
}
}
get the value
<script>
app.value('myValue', #ConfigManager.IsDebugMode());
<script>
On your angular controller
app.controller('ctrl', function($scope, myValue) {
//use myValue
$scope.isTestMode = myValue;
});
html
<div data-ng-show="isTestMode" data-ng-include="'App/Development/development.html'"></div>

Google Analytics don't seem to be firing

I've tried to look around many threads here around GA, I feel like I've done what's required. My site consist of HTML5 and angular.js, the page is structured roughly as follow (through ng-include)
index.html
|-header
--|-navigation
|-content
|-footer
when a link from navigation bar is clicked, the content will change (partials page in angular), header, footer stays the same.
The last script tag in my < head > is as follow (inside index.html)
<script type="text/javascript">
var globalLanguage = 'en';
// GA tracking variable
var _gaq = [['_setAccount', 'UA-XXXXXXXX-X'], ['_trackPageview']];
</script>
and at the bottom part of index.html, before the closing < / body > tag:
<script type="text/javascript">
(function(d, t) {
var g = d.createElement(t), s = d.getElementsByTagName(t)[0];
g.async = 1;
g.src = '//www.google-analytics.com/ga.js';
g.type = 'text/javascript';
s.parentNode.insertBefore(g, s);
}(document, 'script'));
</script>
All my angular controller function, call a common function as described in Tracking Google Analytics Page Views with Angular.js
function gaqPageView($scope, $location, $window) {
console.log('triggering google analytics');
$scope.$on('$viewContentLoaded', function(event) {
console.log('event triggered, tracking: ' + $location.path());
$window._gaq.push([ '_trackPageview', $location.path() ]);
});
}
I do see the console log statements, there's no error in the console either.
When I print out the content of _gaq, I do get an array that grows as I navigate around the page (which mean my _gaq.push call is working just fine).
However, in my the network call (in chrome dev tool), I don't see any _utm.gif call to Google Analytics. (Basics of Debugging Google Analytics Code: GA Chrome Debugger and other tools).
What am I missing here? seems like the google analytics is not firing off the event and reporting it?
Edit: I am pretty sure I am silly here, the _gaq variable itself is just a normal javascript array, so of course _gaq.push work just fine. But what am I missing to get Google Analytics to kick in and start sending the content of that _gaq?
Are you currently running your server on localhost, or an intranet name without a "." -- the tracking GIF request doesn't get made for localhost servers by default.
See Google Analytics GIF request not sent.
Another idea: Usually _gaq is defined as an array only if it's not already defined. If ga.js has already executed, you might be overwriting the _gaq object. It doesn't seem likely from your code organization, but...
Try replacing
var _gaq = [['_setAccount', 'UA-XXXXXXXX-X'], ['_trackPageview']];
with
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXXXXX-X'], ['_trackPageview']);

Unable to access NameSpace.app variables with ST2?

I'm using the Sencha Command Line 3 tools with a newly generated Sencha Touch 2 application.
Assuming my app.js file looks like this:
Ext.application({
name: "CA",
event_code: "test123",
launch: function() {
console.log("application launched!");
}
});
My views and object stores depend on generating a URL based on CA.app.event_code equaling "test123";
During development in the browser, everything works fine, CA.app returns the variables I need.
When I compile my application with sencha app build and try to run the minified version in the browser, I get an error like this:
Error evaluating http://localhost:8888/app.js with message: TypeError: Cannot read property 'event_code' of undefined localhost:11
I'm not entirely sure why this is happening or how I can fix it. I am open to any and all ideas or suggestions, any pointers in the right direction will be greatly appreciated.
Ran into the exact same issue. You have no access to the namespaced app within the views... really sucks that they let you in development and not when built. Anyway, I got around it by adding a static helper class and using that all over my app:
In /app/util/Helper.js:
Ext.define('MyApp.util.Helper', {
singleton: true,
alternateClassName: 'Helper',
config: {
foo: "bar",
bat: "baz"
},
staticFunction: function() {
// whatever you need to do...
}
});
Then in your view or controller:
Ext.define('MyApp.view.SomeView', {
...
requires: ['Events.util.Helper'],
...
someViewFunction: function() {
var someValue = Helper.staticFunction();
// and you can use Helper.foo or Helper.bat in here
}
});
For reference, here's some documentation on Sencha Singletons. And one important note: make sure that your Helper singleton is in it's own file! If it's small, you may be inclined to put it at the bottom of your app.js, and things will work at first, and the build process will work, but the code will not. Don't worry, the build process puts all of your JS code in one big, compressed file anyway.

Resources