We have an Outlook VSTO component that adds a panel that contains a WebBrowser component, which in turn opens a web page.
We want to call back from the web page using WebBrowser.ObjectForScripting, but the guidance provided by MS here doesn't work.
So in the C# VSTO we have something like:
[ComVisible(true),
PermissionSet(SecurityAction.Demand, Name="FullTrust")]
public class MyComponent { ...
webBrowser1.ObjectForScripting = this;
webBrowser1.Document.InvokeScript("test");
...
public void HandleResult() { ...
And in the JS we have something like:
function test() {
doSomethingAsync().then(function(result) {
window.external.HandleResult();
});
}
However HandleResult is never called.
I think the issue it due to the PermissionSet being denied permissions in a VSTO add-in that it does get in stand alone Windows Forms apps.
Any idea how to set the appropriate permissions?
Related
I'm building a login page that, upon submitting and validation of the user credentials, opens up a native mobile application. Up till last week, I had this working cross mobile OS by using a custom scheme URI, something like:
function onLoginCallback() {
const redirectUri = 'customscheme:/action?param1=val1¶m2=val2';
window.location.replace(redirectUri);
}
The login page is displayed in an IABT, short for In App Browser Tab.
However, since the release of version 61 of Chrome, this is approach is broken on Android. Chrome blocks the redirect because there's no apparent user action related to the redirect (see here for more information on the matter).
As a consequence, when executing the code above, I'll end up with a warning in the console:
Navigation is blocked: customscheme:/action?param1=val1¶m2=val2
I've also tried updating the custom scheme url to an intent url but to no avail. Googling about this issue doesn't readily provide a clear solution, so I'm hoping anyone on can help me out.
Edit: Tried to reproduce the issue with the following scenario (as close as possible to the real life scenario):
IABT displays a page with a single button
Clicking the button fires an jsonp call to a mock endpoint
The JSONP callback is executed and fires off a custom event
An event handler for the custom event is triggered and redirects the browser to another mock endpoint
That mock endpoint responds with a 302 to the custom deeplink scheme.
Alas, this seems to be working. I would have expected that the inclusion of the jsonp call would cause Chrome to block the final redirect as it would not be able to identify it as a user initiated action.
Edit 2: Managed to get a reproducible scenario. We've set up a dummy endpoint, that upon request simply returns a 302 with the custom scheme in the Location header. This is blocked on all tries, except for the first one. That fact still boggles the mind. We're using the AppAuth for Android application to test the setup.
I'm opening a custom tab to the endpoint as shown below. The code is taken from this answer.
void launchTab(Context context, Uri uri){
final CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
#Override
public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabsClient client) {
final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
final CustomTabsIntent intent = builder.build();
client.warmup(0L); // This prevents backgrounding after redirection
intent.launchUrl(context, uri);
}
#Override
public void onServiceDisconnected(ComponentName name) {
}
};
CustomTabsClient.bindCustomTabsService(context, "com.android.chrome", connection);
}
We ended up implementing our login and registration forms with a classic post-redirect-get pattern.
The server responds with a 302 to the custom URI scheme. Because in this setup there's no asynchronous execution between the user submitting the form and the browser receiving a redirect, Chrome correctly identifies the chain of actions as trusted and thus will not block the navigation.
I realise this might not be the preferred solution for everyone. A possible alternative to support asynchronous execution flows is the use of universal links as these use regular http(s) schemes, to which redirects were (at the time of posting my question) not considered harmful by Chrome.
For those who use App Auth client and Identity Server:
Startup.cs
services.AddTransient<IAuthorizeResponseGenerator, AuthorizeRG>();
AuthorizeRG.cs
public class AuthorizeRG: AuthorizeResponseGenerator
{
public override async Task<AuthorizeResponse> CreateResponseAsync(ValidatedAuthorizeRequest request)
{
var response = await base.CreateResponseAsync(request);
if (response.RedirectUri != null && request.IsNativeClient())
//this fix chrome navigation blocked on native clients https://bugs.chromium.org/p/chromium/issues/detail?id=738724
response.Request.RedirectUri = $"/native/redirect/{HttpUtility.UrlEncode(response.RedirectUri)}";
return response;
}
}
NativeController.cs
[Route("[controller]")]
public class NativeController : Controller
{
[HttpGet("Redirect/{redirectUri}")]
public IActionResult Redirect([FromRoute] string redirectUri)
{
redirectUri = HttpUtility.UrlDecode(redirectUri);
redirectUri += HttpContext.Request.QueryString.ToUriComponent();
return this.LoadingPage("Redirect", redirectUri);
}
}
Extensions.cs
/// <summary>
/// Checks if the redirect URI is for a native client.
/// </summary>
/// <returns></returns>
public static bool IsNativeClient(this AuthorizationRequest context)
{
return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal)
&& !context.RedirectUri.StartsWith("http", StringComparison.Ordinal);
}
public static bool IsNativeClient(this ValidatedAuthorizeRequest context)
{
return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal)
&& !context.RedirectUri.StartsWith("http", StringComparison.Ordinal);
}
public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri)
{
controller.HttpContext.Response.StatusCode = 200;
controller.HttpContext.Response.Headers["Location"] = "";
return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri });
}
This works for me, but please comment if it broke smth in your authorization flow
I am trying to inject a javascript namespace with function from App to Android WebView
NOTE: I do not have any control on the actual source being loaded on WebView apart from the below script. I am working in Xamarin so the Android code is C#
So there are two major components that I am using:
(function() {
console.log("Loading JS Bindings");
function addOnClickHandler () {
$(document).ready(function () {
if (window.InjectedNamespace) { // check InjectedNamespace namespace exists
var onClickHandler = window.InjectedNamespace.handleOnClick;
if (typeof onClickHandler === 'function') {
$(":button, a").each(function (idx) {
$(this).click(function() {
onClickHandler("click!");
});
});
} else {
console.error('missing onclick handler');
}
} else {
console.error('missing InjectedNamespace namespace');
}
});
}
})();
Above script which will be added by the third-party, which will attach click function to all <button> and <a> tags.
Below namespace will be injected from the App side
var InjectedNamespace={handleOnClick:function(a){Internal.handleOnClickEvent(a)}};
On App side I have this Javascript interface object
class JSInterfaceObject : Java.Lang.Object
{
readonly CustomWebView View;
string Data;
public JSInterfaceObject(CustomWebView view)
{
View = view;
}
[Export]
[JavascriptInterface]
// only methods exposed with annotation [JavascriptInterface] are exposed to javascript
public void handleOnClickEvent(string data)
{
Log.ForContext("TAG", TAG).Debug("handleOnClickEvent: " + data);
if(View.OnClick != null)
{
View.OnClick(data);
}
}
}
And this interface is added to webview using
AddJavascriptInterface(new JSInterfaceObject(this), "Internal");
And in OnPageStarted I am injecting the javascript using
webview.LoadUrl("javascript:"+"var IM={handleOnClick:function(a){Internal.handleOnClickEvent(a)}};");
This setup work fine for Android SDK < 24. But for Webview in SDK >=24 the script always errors out with missing InjectedNamespace namespace, which implies the LoadUrl in OnPageStarted failed!
This check is done in $(document).ready in the script.
I found this note in "Android 7.0 for Developers" which says
"Starting with apps targeting Android 7.0, the Javascript context will
be reset when a new page is loaded. Currently, the context is carried
over for the first page loaded in a new WebView instance.
Developers looking to inject Javascript into the WebView should
execute the script after the page has started to load."
So tried to add the javascript injection code after the page started loading but got the same error. Also tried using the WebView.EvaluateJavascript() but error persists.
The error disappers if I change my TargetSDK to <=23.
SO community
I'm currently writing a project to integrate some custom views and forms in Outlook.
The idea is to create an Outlook extension connected to our ERP.
As discovered in this sample project (http://msdn.microsoft.com/en-us/library/aa479346.aspx), one technique is to build an ActiveX component and load it from a lightweight local html page, like this :
<html>
<body rightmargin = '0' leftmargin ='0' topmargin ='0' bottommargin = '0' onload='OnBodyLoad()'>
<object classid='clsid:f746a8b6-3659-4f4c-8518-6336187854f2' ID='MyView' VIEWASTEXT width='100%' height='100%'/>
<script>
function OnBodyLoad() {
try {
var oApp = window.external.OutlookApplication;
var view = document.getElementById('MyView');
view.Initialize(oApp);
}
catch(err) {
alert(err.description);
}
}
</script>
</body>
</html>
As you can see the Outlook application instance is passed to the Initialize() method for further access from the ActiveX (this is the important point for me).
The C# class looks like this :
[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.Guid("F746A8B6-3659-4F4C-8518-6336187854F2")]
public partial class MyView : UserControl
{
public MyView()
:base()
{
Application.EnableVisualStyles();
InitializeComponent();
}
public String Str { get; set; }
public void Initialize(Object app)
{
MessageBox.Show("Initialized");
//MessageBox.Show(app.Version);
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(this, "It works!");
}
}
It's signed and registered with RegAsm.
When I load it in IE, it works like a charm. I get the message "Initialized" at startup, and when I click on button1, the message "It works" pops up too.
BUT (there is of course a 'but' :) when I load this page in Outlook (folder properties / home page), the ActiveX component gets loaded and visible, but the Initialize call throws this error: "Object doesn't support this property or method". Clicking the button1 works.
Trying to assign something to the property from Str with javascript fails in Outlook too, and works in IE.
Did anyone here face the same problem, and knows what I'm missing?
Many thanks in advance,
Nicolas
I don't want to open the browser but the actual store in my Windows 8 phone.
I am developing an app using PhoneGap and so I want to do this with Javascript.
I haven't submitted my app so I don't yet have a package name. How do I test this without an actual package name?
Also, I can't seem to be able to use:
Windows.System.Launcher.LaunchUriAsync(new Uri(appStoreURL));
I get:
Error:["'Windows' is undefined file:x-wmapp0:www\/js\/......
Any ideas?
SOLUTION:
Using Benoit's answer and some other stuff I found I managed to link straight to the review section by adding the following Plugin to my cordovalib:
LaunchReview.cs
using WPCordovaClassLib.Cordova.Commands;
using Microsoft.Phone.Tasks;
namespace Cordova.Extension.Commands
{
public class LaunchReview : BaseCommand
{
public void launchReview(string options)
{
// Use the Marketplace review task to launch the Store or Marketplace and then display the review page for the current app.
MarketplaceReviewTask marketplaceReviewTask = new MarketplaceReviewTask();
marketplaceReviewTask.Show();
}
}
}
Note sure what value you are using for appurl but here is something which should work:
Windows.System.Launcher.LaunchUriAsync(new Uri("zune:reviewapp"));
or you can use:
MarketplaceReviewTask marketplaceReviewTask = new MarketplaceReviewTask();
marketplaceReviewTask.Show();
To call it from javascript just create a plugin:
namespace Cordova.Extension.Commands
{
public class LaunchReview: BaseCommand
{
public void launchReview(string options)
{
// all JS callable plugin methods MUST have this signature!
// public, returning void, 1 argument that is a string
MarketplaceReviewTask marketplaceReviewTask = new MarketplaceReviewTask();
marketplaceReviewTask.Show();
}
}
}
that you can use it like this from javascript:
cordova.exec(win, fail, "LaunchReview", "launchReview", [""]);
Here is the link to the plugin dev guide for windows phone
If you want to use window.open then you will need to modify the PhoneGap source code to use LAunchUri because currently it's just using WebBrowserTask instead of LaunchUri. The function to modify is Plugin/InAppBrowser.cs>ShowSystemBrowser
I used InAppBrowser cordova plugin.
cordova plugin add org.apache.cordova.inappbrowser
To open wp8 store i call from javascript:
window.open(UrlToMyApp, '_blank', 'location=yes');
I want to create an application where a web server can get the MAC Address of the clients logging in. The only possible way I could think of was to create a JAVA applet which contains java.net methods to find the mac address
I am using javascript to call the applet methods, but the browser is not allowing those methods to execute. Below is the applet I have created.
import java.applet.*;
import java.awt.*;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
public class AppletRunner extends Applet{
// The method that will be automatically called when the applet is started
public void init()
{
// It is required but does not need anything.
}
//This method gets called when the applet is terminated
//That's when the user goes to another page or exits the browser.
public void stop()
{
// no actions needed here now.
}
//The standard method that you have to use to paint things on screen
//This overrides the empty Applet method, you can't called it "display" for example.
public void paint(Graphics g)
{
//method to draw text on screen
// String first, then x and y coordinate.
g.drawString(getMacAddr(),20,20);
g.drawString("Hello World",20,40);
}
public String getMacAddr() {
String macAddr= "";
InetAddress addr;
try {
addr = InetAddress.getLocalHost();
System.out.println(addr.getHostAddress());
NetworkInterface dir = NetworkInterface.getByInetAddress(addr);
byte[] dirMac = dir.getHardwareAddress();
int count=0;
for (int b:dirMac){
if (b<0) b=256+b;
if (b==0) {
macAddr=macAddr.concat("00");
}
if (b>0){
int a=b/16;
if (a==10) macAddr=macAddr.concat("A");
else if (a==11) macAddr=macAddr.concat("B");
else if (a==12) macAddr=macAddr.concat("C");
else if (a==13) macAddr=macAddr.concat("D");
else if (a==14) macAddr=macAddr.concat("E");
else if (a==15) macAddr=macAddr.concat("F");
else macAddr=macAddr.concat(String.valueOf(a));
a = (b%16);
if (a==10) macAddr=macAddr.concat("A");
else if (a==11) macAddr=macAddr.concat("B");
else if (a==12) macAddr=macAddr.concat("C");
else if (a==13) macAddr=macAddr.concat("D");
else if (a==14) macAddr=macAddr.concat("E");
else if (a==15) macAddr=macAddr.concat("F");
else macAddr=macAddr.concat(String.valueOf(a));
}
if (count<dirMac.length-1)macAddr=macAddr.concat("-");
count++;
}
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
macAddr=e.getMessage();
} catch (SocketException e) {
// TODO Auto-generated catch block
macAddr = e.getMessage();
}
return macAddr;
}
}
Applets cannot normally access these functions for security reasons. To avoid these restrictions, you need a signed applet, along with a policy file.
You can then write a policy file which grants your applet access to the functionality it needs. If the user then grants your applet the necessary permissions (it will prompt for them), your applet can use the functions.
In Netbeans, you can sign an application enabling the WebStart:
Access to Your project > properties > Application > WebStart
Check "Enable Web Start". This show a sectin titled signing.
Click the "Customize" button located in the signing section.
Select "self-sign by generated key".
I don't think this will be possible. Web servers communicate with clients several layers above the link layer where MAC addresses live -- it's abstracted away by TCP/IP and there's no reason for the client to send it unless you specifically have client code to do that.
The reason your Java code isn't working is because the Java sandbox's security manager disallows such low-level calls -- which it should! If you ever do find a way to get that thing to work (which I doubt you will) you should promptly report it to Oracle because it shouldn't be happening at all.
I can't see much of a reason why you'd want it either, to be honest.
The Java applet is prevented to access those methods on the client because it runs in a protected sandbox.
It might not be possible within a browser, since it is against the sandboxing paradigm. You might have some luck with browser-specific native code extensions.
However, the important exception is if your web server is in the same local area network (same switch) as the client - then, the MAC address of the client is known to the server because it is still present in the IP packet.