Disclosure Statement: This site contains affiliate links, which means that I may receive a commission if you make a purchase using these links. As an eBay Partner, I earn from qualifying purchases.

Receiving Callback URI from Navigation

Post Reply
rackel
Posts: 2
Joined: Thu Nov 12, 2020 8:32 am

Receiving Callback URI from Navigation

Post by rackel »

Hello,

in order to communicate with an online API I have to receive callbacks send by the page while I'm navigating in it. As written in the API's documentation, they contain the keyword "callback" e.g. "http://api.ausschreiben.de/v2/callback/ ... /startpage".
My Problem there is, that I don't receive them at all.
I'm using the latest CEF4Delphi on Tokyo 10.2.3 and tested in "SimpleBrowser" Demo.
I expected to receive them in the OnBeforeBrowes-Event or the OnAdressChange-Event but i am getting none.

In the API's demo (which is working), the C# looks like this:

Code: Select all

/// <summary>
/// eventhandler to receive callbacks
/// </summary>
private void WebBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
	if (mNavigationCallbackHandler != null)
	{
		//if it is a callback, the navigation has to be intercepted
		if (mNavigationCallbackHandler.Callback(e.Url))
			e.Cancel = true;
	}
}

Code: Select all

type
  TForm1 = class(TForm)
    ChromiumWindow1: TChromiumWindow;
    AddressPnl: TPanel;
    AddressEdt: TEdit;
    GoBtn: TButton;
    Timer1: TTimer;
    btnApi: TButton;

    procedure btnApiClick(Sender: TObject);
    procedure GoBtnClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);

    procedure FormShow(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormCreate(Sender: TObject);

    procedure ChromiumWindow1AfterCreated(Sender: TObject);
    procedure ChromiumWindow1Close(Sender: TObject);
    procedure ChromiumWindow1BeforeClose(Sender: TObject);
    procedure wb1BeforeNavigate2(ASender: TObject; const pDisp: IDispatch; const
        URL, Flags, TargetFrameName, PostData, Headers: OLEVariant; var Cancel:
        WordBool);

  private
    FApiSession: string;
    // You have to handle this two messages to call NotifyMoveOrResizeStarted or some page elements will be misaligned.
    procedure WMMove(var aMessage : TWMMove); message WM_MOVE;
    procedure WMMoving(var aMessage : TMessage); message WM_MOVING;
    // You also have to handle these two messages to set GlobalCEFApp.OsmodalLoop
    procedure WMEnterMenuLoop(var aMessage: TMessage); message WM_ENTERMENULOOP;
    procedure WMExitMenuLoop(var aMessage: TMessage); message WM_EXITMENULOOP;

    function HttpPostExecute(AUrl, PostBodyData: string): string;
    function GetResultUriFromResponse(response: string): string;
    function GetApiSessionFromResponse(response: string): string;
    procedure BeforeHeaderSendHandler(Sender: TObject; const Method: string; Headers: TStrings);

  protected
    // Variables to control when can we destroy the form safely
    FCanClose : boolean;  // Set to True in TChromium.OnBeforeClose
    FClosing  : boolean;  // Set to True in the CloseQuery event.

    procedure Chromium_OnBeforePopup(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; const targetUrl, targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean; const popupFeatures: TCefPopupFeatures; var windowInfo: TCefWindowInfo; var client: ICefClient; var settings: TCefBrowserSettings; var extra_info: ICefDictionaryValue; var noJavascriptAccess: Boolean; var Result: Boolean);
    procedure chrOnAdressChange(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; const url: ustring);
    procedure chrOnQuotaRequest(Sender: TObject; const browser: ICefBrowser; const originUrl: ustring; newSize: Int64; const callback: ICefRequestCallback; out Result: Boolean);
    procedure chrOnResourceResponse(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; const request: ICefRequest; const response: ICefResponse; out Result: Boolean);
    procedure chrOnBeforeBrowse(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; const request: ICefRequest; user_gesture, isRedirect: Boolean; out Result: Boolean);
    procedure chrOnLoadStart(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; transitionType: TCefTransitionType);
    procedure chrResourceRedirect(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; const request: ICefRequest; const response: ICefResponse; var newUrl: ustring);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  uCEFApplication,
  IdHTTP,
  SuperObject,
  OverbyteIcsHttpProt,
  uCEFRequest,
  uCEFStringMultimap,
  uCEFStringMap,
  uCEFPostData,
  StrUtils;

const
   SOFTWARE_ID = '865f215d-da17-11e5-8132-00241dc1eecb';
   BASE_URL = 'http://api.ausschreiben.de/v2/';
   STARTSESSION_ROUTE = 'startsession/';
   ENDSESSION_ROUTE = 'endsession/';
   EXPORT_DATA_ROUTE = 'export/';
   HTTP_RESPONSE_OK = 200;

procedure TForm1.btnApiClick(Sender: TObject);
var
  response, uri : string;
begin
  // starting the API-session via HTTP-Post
  response := HttpPostExecute('http://api.ausschreiben.de/v2/startsession/software/' + SOFTWARE_ID + '/viewmode/default/location/startpage/registercallbacks/dragstart,export,locationchanged', '');
  // receive the session ID
  FApiSession := GetApiSessionFromResponse(response);
  // navigate to the opened session
  uri := GetResultUriFromResponse(response);
  ChromiumWindow1.LoadURL(uri);
end;

procedure TForm1.chrOnBeforeBrowse(Sender: TObject; const browser: ICefBrowser;
  const frame: ICefFrame; const request: ICefRequest; user_gesture,
  isRedirect: Boolean; out Result: Boolean);
begin
  // detecting the callback on navigation
  if AnsiContainsStr(request.Url, 'callback') then
  begin
    // do something
  end;
end;

function TForm1.GetResultUriFromResponse(response: string): string;
var
  O: ISuperObject;
begin
   O := SO(response);
   Result := O.S['ResultUri'];
end;

function TForm1.GetApiSessionFromResponse(response: string): string;
var
  O: ISuperObject;
begin
   O := SO(response);
   Result := O.S['ApiSession'];
end;

procedure TForm1.BeforeHeaderSendHandler(Sender: TObject;
  const Method: string; Headers: TStrings);
begin
   Headers.Add('Accept: application/json');
   if FApiSession <> '' then begin
      Headers.Add('X-API-Session: ' + FApiSession);
   end;
end;

function TForm1.HttpPostExecute(AUrl, PostBodyData: string): string;
var
   httpClient: TSslHttpCli;
   responseData: TStringStream;
begin
   Result := '';
   httpClient := TSslHttpCli.Create(nil);
   responseData :=  TStringStream.Create('');
   try
      httpClient.RcvdStream := responseData;
      httpClient.SendStream := TStringStream.Create('');
      httpClient.URL := AUrl;
      TStringStream(httpClient.SendStream).WriteString(PostBodyData);
      httpClient.SendStream.Position := 0;
      httpClient.OnBeforeHeaderSend := BeforeHeaderSendHandler;
      try
         httpClient.Post;
      except
         // nothing to do here
      end;
      if httpClient.StatusCode = HTTP_RESPONSE_OK then  begin
         result := responseData.DataString;
      end;
   finally
      responseData.Free;
      httpClient.SendStream.Free;
      httpClient.Free;
   end;
end;

procedure TForm1.chrOnAdressChange(Sender: TObject; const browser: ICefBrowser;
  const frame: ICefFrame; const url: ustring);
begin
//
end;
Is there anything I can do to get to them navigation-callbacks?

kind regards
User avatar
salvadordf
Posts: 4016
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Receiving Callback URI from Navigation

Post by salvadordf »

Hi,

I don't know the ausschreiben.de API but if you used TWebBrowser.OnBeforeNavigate2 to check if the new URL had the "callback" text then you should be able to do the same in the TChromium.OnBeforeBrowse.

If the browser was redirected to another URL I would also check TChromium.OnResourceRedirect. Other event that should work is TChromium.OnAddressChange.

If you still have problems, please check that the browser is not blocking the navigation for another reason like : too many redirects, blocked CORS redirects, etc.

On another note : I would suggest you to use the SimpleBrowser2 demo because the TChromiumWindow component used by the SimpleBrowser demo is only intended for extremely simple browsers. You need to use TChromium events and that's easier if you use a TChromium + TCEFWindowParent pair as you can see in the SimpleBrowser2 demo.
rackel
Posts: 2
Joined: Thu Nov 12, 2020 8:32 am

Re: Receiving Callback URI from Navigation

Post by rackel »

Thank you for the quick response!

I tried it in SimpleBrowser2 now and I still don't get the callbacks.
The thing is: I placed a TWebbrowser component next to the TChromium for comparison and let him run the same code.
However in the TWebbrowser.OnBeforeNavigate2 (as you mentioned) I do get the callbacks e.g. picture:

Image

But in the TChromium.OnBeforeBrowse, TChromium.OnResourceRedirect nor TChromium.OnAdressChange I am not getting the same result.
I am kind of clueless at this point.

Code: Select all

const
   SOFTWARE_ID = '865f215d-da17-11e5-8132-00241dc1eecb';
   BASE_URL = 'http://api.ausschreiben.de/v2/';
   STARTSESSION_ROUTE = 'startsession/';
   ENDSESSION_ROUTE = 'endsession/';
   EXPORT_DATA_ROUTE = 'export/';
   HTTP_RESPONSE_OK = 200;

procedure TForm1.btn1Click(Sender: TObject);
var
  response, uri : string;
begin
  // starting the API-session via HTTP-Post
  response := HttpPostExecute('http://api.ausschreiben.de/v2/startsession/software/' + SOFTWARE_ID + '/viewmode/default/location/startpage/registercallbacks/dragstart,export,locationchanged', '');
  // receive the session ID
  FApiSession := GetApiSessionFromResponse(response);
  // navigate to the opened session
  uri := GetResultUriFromResponse(response);
  wb1.Navigate(uri);
  Chromium1.LoadURL(uri);
end;

function TForm1.GetResultUriFromResponse(response: string): string;
var
  O: ISuperObject;
begin
   O := SO(response);
   Result := O.S['ResultUri'];
end;

function TForm1.GetApiSessionFromResponse(response: string): string;
var
  O: ISuperObject;
begin
   O := SO(response);
   Result := O.S['ApiSession'];
end;

procedure TForm1.BeforeHeaderSendHandler(Sender: TObject;
  const Method: string; Headers: TStrings);
begin
   Headers.Add('Accept: application/json');
   if FApiSession <> '' then begin
      Headers.Add('X-API-Session: ' + FApiSession);
   end;
end;

function TForm1.HttpPostExecute(AUrl, PostBodyData: string): string;
var
   httpClient: TSslHttpCli;
   responseData: TStringStream;
begin
   Result := '';
   httpClient := TSslHttpCli.Create(nil);
   responseData :=  TStringStream.Create('');
   try
      httpClient.RcvdStream := responseData;
      httpClient.SendStream := TStringStream.Create('');
      httpClient.URL := AUrl;
      TStringStream(httpClient.SendStream).WriteString(PostBodyData);
      httpClient.SendStream.Position := 0;
      httpClient.OnBeforeHeaderSend := BeforeHeaderSendHandler;
      try
         httpClient.Post;
      except
         // nothing to do here
      end;
      if httpClient.StatusCode = HTTP_RESPONSE_OK then  begin
         result := responseData.DataString;
      end;
   finally
      responseData.Free;
      httpClient.SendStream.Free;
      httpClient.Free;
   end;
end;

procedure TForm1.wb1BeforeNavigate2(ASender: TObject; const pDisp: IDispatch;
    const URL, Flags, TargetFrameName, PostData, Headers: OLEVariant; var
    Cancel: WordBool);
begin
  if AnsiContainsStr(Url, 'callback') then
  begin
    // do something
  end;
end;

procedure TForm1.Chromium1AddressChange(Sender: TObject; const browser:
    ICefBrowser; const frame: ICefFrame; const url: ustring);
begin
  if AnsiContainsStr(Url, 'callback') then
  begin
    // do something
  end;
end;

procedure TForm1.Chromium1BeforeBrowse(Sender: TObject; const browser:
    ICefBrowser; const frame: ICefFrame; const request: ICefRequest;
    user_gesture, isRedirect: Boolean; out Result: Boolean);
begin
  if AnsiContainsStr(request.Url, 'callback') then
  begin
    // do something
  end;
end;

procedure TForm1.Chromium1ResourceRedirect(Sender: TObject; const browser:
    ICefBrowser; const frame: ICefFrame; const request: ICefRequest; const
    response: ICefResponse; var newUrl: ustring);
begin
  if AnsiContainsStr(request.Url, 'callback') then
  begin
    // do something
  end;
end;
User avatar
salvadordf
Posts: 4016
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Receiving Callback URI from Navigation

Post by salvadordf »

I tried to read the API documentation but I couldn't find it on that website.

Anyway, I see that you are adding a "X-API-Session" header to the TSslHttpCli request and perhaps you need to add that header to the TChromium requests.

Try using the TChromium.CustomHeaderName and TChromium.CustomHeaderValue properties to add a custom header name and value to the TChromium requests.

One more suggestion : Replace TSslHttpCli with a TCEFUrlRequestClientComponent. Take a look at the URLRequest demo for more details about using TCEFUrlRequestClientComponent. TCEFUrlRequestClientComponent uses the same cache and cookies as TChromium and that might be useful to keep sessions open.
Luz
Posts: 1
Joined: Thu Jul 02, 2020 12:28 pm

Re: Receiving Callback URI from Navigation

Post by Luz »

Hello rackel,
I Have a similar problem with the API of ausschreiben.de.
Could yo solve your problem?
In our D10.2.3 Application I stuck with the line wbChromium.LoadURL(FResultObj.ResultUri);
The ResultUri is correctly given by a TCEFUrlRequestClientComponent, which was filled in the event DownloadData.
Here you can see my FResponsetext:
{"ApiSession":"273fdb10-b806-4af3-9e74-1a8c4e177f7f","RequestedIEVersion":[11000,11001],"CallbackSourceOrigin":"https://www.ausschreiben.de/","RegisteredCallbacks":["dragstart","export","locationchanged"],"InterfaceVersion":"2.1.0.0","InterfaceBaseUrl":"https://api.ausschreiben.de/v2","IsSuccess":true,"ResultUri":"https://www.ausschreiben.de:443/?sessionId=273fdb10-806-4af3-9e74-1a8c4e177f7f","Error":null,"ErrorCode":0,"DisplayTitle":null,"DisplayMessage":null,"DebugHints":null,"ServerTimeMs":0,"RunTimeMs":0}

I use this code to get the data in my class FResultObj
FMemStream.WriteBuffer(data^, dataLength);
FResponseText := MemoryStreamToString(FMemStream);
FResultObj := TJson.JsonToObject<TResultSessionData>(FResponseText);

As they describe in their API-Documentation, I use the ResultUri to show the Page (wbChromium.LoadURL(FResultObj.ResultUri)).
But the Browserwindow always show : "Hoppla...Die gewünschte Seite ist gerade nicht erreichbar". In english : The site is not available.

I don't find out what went wrong here. Is there any property to set in the TChromium-component?
What was your solution, if you found one?
greetings Luz
Post Reply