Converting WebGMaps to FNC Maps, How to set AuthorizedURL?

I'm converting a WebGMaps app (version 2.0.1.0) to FNC Maps to prepare for the pending demise of IE support in the next few months from Google and having some difficulty.

Because WebGMaps loaded HTML manually I used this fix below I found online many years ago to specify the AuthorizedURL for TWebBrowser (required for GoogleMaps for Work) but I cannot figure out how to do this (or similar) converting the WebGMaps app to FNC Maps.

    { fix for TWebBrowser loading document has no URL to authorize for Google Maps for Work }
    { note this fix seems to only work the first time Map.Launch() is called }
    (WebBrowser.Document as IHTMLDocument2).URL := GetAuthorizedURL; 

When the AuthorizedURL is not set the follow error message is displayed in FNC maps

Oops! Something went wrong. This page didn't load Google Maps correctly. See the JavaScript console for technical details.

Inspecting the browser console it shows this underlying error...

Google Maps JavaScript API error: UnauthorizedURLForClientIdMapError
Error Messages  |  Maps JavaScript API  |  Google Developers, Your site URL to be authorized: file:///C:/Users/********/AppData/Local/Temp/%7BABE6C9AB-C7C9-49C8-8B98-C9A793731C94%7D.html

Note I used the OnCustomizeHeadLinks in order to swap out the key=value with ClientID & signature which was required at the time we built our google maps solution with Google Maps for Work. Previously in WebGMaps app (version 2.0.1.0) we had to hardcode the URL change for ClientID and Signature to get WebGMaps it to work with Google Maps for Work.

The default origin for edge chromium is about:blank and cannot be changed (Set origin with NavigateToString · Issue #530 · MicrosoftEdge/WebView2Feedback · GitHub). This is currently a shortcoming in Edge Chromium APIs. I notice you are having LocalFileAccess := True, which then saves the HTML / JS as files in a temporary folder. Each time the application starts, the GUID changes and this creates a new file. It's also unclear if file:// URLs can be registered, I suppose not. We'll further monitor and investigate this here, but the client authorization flow is currently not supported due to technical shortcomings in the browser implementation.

Hello Pieter,

I tried to get around this problem by saving a copy of the generated web page by FNC maps and hosting it so I could use TTMSFNCMaps.Navigate() instead of load from file. This kinda works but ends up being very inconsistent when loading the map. For example I put a button on the form that calls the navigate method to my saved copy of the hosted HTML maps being file but sometimes the map doesn't load or loads partially (non-functional, partially drawn).

Perhaps after 2 or 3 tries of calling the Navigate() method it finally seems to load successfully. It's like perhaps internals of TTMSFNCMaps may not be initialized properly when using Navigate() vs. the default method of loading the HTML string that the component currently uses. In TMSWebGMaps I was able to utilize the OnDocumentComplete event to ensure maps was completely loaded before working with it but I cannot seem to tap into the same concept with TTMSFNCMaps. I suspect if I can wait until the document is completely loaded when using Navigate() the problem may correct itself.

Do you have any advice or can you help me going down this path to get Navigate() to successfully work?

Using the Navigate, will completely ignore the linking with TTMSFNCMaps, you are basically using a browser instead going to an HTML file, the code internally will not be able to link to the HTML. Aside from that, you could use the OnMapInitialized event, which is called when the map is initialized. It's unclear which effects this has, as this is not the way TTMSFNCMaps is designed, but it's worth a try.

Going back to the stock code using API key

The closest workaround I can get working with AuthroizedURL's seems to be configuring the key in google maps with

(When NOT using LocalFIleAccess)

about:blank

or

(When using LocalFIleAccess the following with a wild card)

__file_url__//C:/Users*

however this does not provide very good protection for our API key.

When LocalFileAccess is false, the default target is about:blank and there is currently no way around this. When LocalFileAccess is true, it randomly generates a file HTML which is then loaded in the browser.

So to further customize this, we added

function GetLocalAccessFileName: string; virtual;

Which allows you to override this. This would mean you need to create a descendant class, specify the full file name and path where the maps HTML need to be saved. Would that be something you can work with?

You could then do something like this:

unit Unit30;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FMX.TMSFNCTypes, FMX.TMSFNCUtils, FMX.TMSFNCGraphics, FMX.TMSFNCGraphicsTypes,
  FMX.TMSFNCCustomControl, FMX.TMSFNCWebBrowser, FMX.TMSFNCMaps,
  FMX.TMSFNCGoogleMaps;

type
  TTMSFNCGoogleMapsEx = class(TTMSFNCGoogleMaps)
  protected
    function GetLocalAccessFileName: string; override;
  end;

  TForm30 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    g: TTMSFNCGoogleMapsEx;
  public
    { Public declarations }
  end;

var
  Form30: TForm30;

implementation

{$R *.fmx}

procedure TForm30.FormCreate(Sender: TObject);
var
  g: TTMSFNCGoogleMapsEx;
begin
  g := TTMSFNCGoogleMapsEx.Create(Self);
  g.Parent := Self;
  g.APIKey := 'APIKEY';
  g.LocalFileAccess := True;
end;

{ TTMSFNCGoogleMapsEx }

function TTMSFNCGoogleMapsEx.GetLocalAccessFileName: string;
begin
  Result := 'C:\MyPath\MyMap.html';
end;

end.

We'll include this in the next release.

Couldn't you do an event handler instead like OnCustomizeLocalAccessFileName similar to the other OnCustomize events?

Ofcourse, We'll introduce a new event OnCustomizeLocalAccessFileName. Available in the next release!

Ok great.

A coworker of mine suggested perhaps the use of SetVirtualHostNameToFolderMapping may helpful in providing a solution but I have not had the chance to experiment or figure out how to access this function within the FNC framework.

I believe Navigate() has the potential of working when called in InitializeHTML() but obviously one must be using a pre-saved copy of the HTML generated with LocalFileAccess = True in order to get the Javascript linking properly hooked into TTMSFNCMaps. There is a (maybe some timing) issue though that causes this to be hit or miss in my testing so far loading the map properly. Loading from file or HTML string seems more immediate and obviously there aren't any issues with that as designed.

Something I noticed when debugging is the behavior of InitializeHTML get's called multiple times making the use of Navigate in that desired location more problematic. In TMSWebGMaps I was able to call Map.Launch to control the Initialization of the HTML web page where as explicit control in FNC maps is now not possible. If we could have an OnCustomizeInitializeHTML event that if assigned would allow the user to override the loading behavior (not do the default load from file or string) that might be helpful.

The reason why the multiple calls to InitializeHTML concerns me is I don't know if that will effect the google usage counters. Previously in our application with TMSWebGMpas I was able to control map loading explicitly like I said using Map.Launch which guaranteed that the google map counter would only be pinged once when the user launched our application. I will do some testing and get back to you on this if we discover a problem.

InitializeHTML is called multiple times when load specific properties are affected, for example APIKey, LocalFileAccess and others. You can keep this under control with BeginUpdate & EndUpdate and only launch the map, by setting the API key once you have everything configured. Setting the APIKey will then effectively load the HTML for the map. While there might be multiple calls to InitializeHTML, the MapReady check will only start loading the actual required HTML when all conditions are set, especially the API key. You can see this here

function TTMSFNCCustomMaps.MapReady: Boolean;
begin
  Result := Assigned(FMaps) and Assigned(FMapsInstance) and Assigned(FMapsProperties) and FMaps.IsValid;
end;

FMaps.IsValid will return only true when the API key is set:

function TTMSFNCMapsGoogleMaps.IsValid: Boolean;
begin
  Result := MapsProperties.GetAPIKey <> '';
end;

You can check for MapReady in the TMSFNCMaps.pas source code.

The SetVirtualHostNameToFolderMapping is something that is not yet exposed. We'll expose the APIs in TMS FNC Core (VCL.TMSFNCWebBrowser.Win unit). You can access the Windows specific APIs and webbrowser instance using this code, once it's available.

uses
  VCL.TMSFNCWebBrowser.Win;

procedure TForm31.Button1Click(Sender: TObject);
var
  c: ICoreWebView2Controller;
  w: ICoreWebView2;
  w3: ICoreWebView2_3;
begin
  c := ICoreWebView2Controller(TMSFNCGoogleMaps1.NativeBrowser);
  if c.get_CoreWebView2(w) = S_OK then
  begin
    if w.QueryInterface(IID_ICoreWebView2_3, w3) = S_OK then
    begin
      w3.SetVirtualHostNameToFolderMapping...
    end;
  end;
end;