Icon View Thread

The following is the text of the current message along with any replies.
Messages 1 to 10 of 10 total
Thread RemObjects SDK, and Channel.OnLoginNeeded
Mon, Mar 11 2013 3:12 PMPermanent Link

Matthew Jones

Has anyone else used the "session required" option for RemObjects SDK? So far I've
not used it, but my latest channel requires it. However, I am not sure how to make
it work in EWB.

The situation is that a message is sent to the server (using http) and an exception
comes back which indicates that a session was not found. This is normal, and the
client is then able to call the Login, and then retry the initial request. The
problem I'm having is in working out how to specify my OnLoginNeeded procedure.

An extract of the javascript is
-------------
RemObjects.SDK.ClientChannel.prototype.dispatch = function dispatch(aMessage,
onSuccessFunction, onErrorFunction) {
   function handleException(e) {
if (((e.name == "EROSessionNotFound") || (e.name ==
"SessionNotFoundException")) && !(that.retrying)) {
           if (that.onLoginNeeded) {
               that.onLoginNeeded(function() {
-------------

This seems to be understandable - it checks if "that.onLoginNeeded" is there, and
then calls it.

In the forums, someone has this Javascript code:

-------------
var channel = new RemObjects.SDK.HTTPClientChannel("http://" + window.location.host
+ "/JSON");
var message = new RemObjects.SDK.JSONMessage();
var service = new WZDService(channel, message);
var loginsvc = new AuthService(channel,message);
channel.onLoginNeeded = function onLoginNeeded(aCallback) {
loginsvc.Login("username","pwd",function(result) {
if (aCallback) {
aCallback();
}
},RemObjects.UTIL.showError);
}
-------------

The problem I have is that my equivalent (without the login service shown) gets a
compile time error:
-------------
   szServer := 'https://' + m_szHostAddress + '/JSON';
   FChannel := SDK.HTTPClientChannel.Create(szServer);
   FMessage := SDK.JSONMessage.Create;
   FSrvc := TPoll123Question.Create(FChannel, FMessage, 'Poll123Question');
   FChannel.onLoginNeeded := OnServerLoginNeeded;
-------------
[Error] fControl.wbs (1093,14): The referenced variable, parameter, or function
onLoginNeeded does not exist. There is no mention of "need" in the generated
javascript interface code. The javascript for the default callback that shows an
error message is:

RemObjects.SDK.ClientChannel.prototype.onLoginNeeded = function
onLoginNeeded(aCallback) {
RemObjects.UTIL.showMessage("Default onLoginNeeded handler: assign
channel.onLoginNeeded and call aCallback there after successful login");
   aCallback();
};

If anyone knows how to link me up, please say! Note also that the type of the
aCallback is undefined. I have defined a type ready for this, but so far not found
a way that works to use it.

TLoginCompletedCallback = procedure of object;

Thanks!

/Matthew Jones/
Mon, Mar 11 2013 3:28 PMPermanent Link

Matthew Jones

Hmm, http://wiki.remobjects.com/wiki/ClientChannel_Object says "onLoginNeeded
Called when the channel receives EROSessionNotFound server exception. Initially
holds a stub and should be replaced with a user function that performs actual login
procedure and calls the callback function."

The EWB code to "import" this javascript is:

  external RemObjects: TRemObjects;
  SDK: TSDK;

Am I able to "inherit" from that and somehow replace or define the onLoginNeeded
function? Do I need to blow my mind tomorrow by working out if this is possible? Or
perhaps it needs some interface javascript which does it. Hmm.

/Matthew Jones/
Tue, Mar 12 2013 6:01 AMPermanent Link

Matthew Jones

Had a word with a friend who does javascript. I now understand the .prototype part.
And that I can have a variable that contains a function. Just got to work out how
to link that.

The EWB help has a section on "external code" that probably contains the answer,
but at the moment it is using terms I don't understand. Studying to follow!

I realised last night that I am demonstrating this to customers on Monday! I can
hack it if needed, but a proper solution would be better.

/Matthew Jones/
Tue, Mar 12 2013 4:10 PMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Matthew,

<< The problem I have is that my equivalent (without the login service
shown) gets a compile time error: >>

What external interface are you using for the RemObjects stuff ?  More than
likely, the OnLoginNeeded event is not defined for the "channel" class.
What you'll need to do is add that event to the external interface, and
assign it the TLoginCompletedCallback type.

If you have any other questions, please let me know.

Tim Young
Elevate Software
www.elevatesoft.com
Wed, Mar 13 2013 7:22 AMPermanent Link

Matthew Jones

I have sent you a cut-down project by email. Partly because I can get it to get an
AV from the compiler, but also because it doesn't appear to be doing what it should,
or at least I can't work out how to get it to.

Obviously once I understand it the RO interface can be updated and documented
better for all.

/Matthew Jones/
Wed, Mar 13 2013 5:46 PMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Matthew,

<< I have sent you a cut-down project by email. Partly because I can get it
to get an AV from the compiler, >>

That's now fixed.  It was this call:

begin
   Report('Login response ' + IntToStr(nResult));
   if nResult = 1 then
   begin
       if assigned(m_pfnLoginRetry) then
       begin
           Report('Found retry fn');
           m_pfnLoginRetry();  <<<<<<<<<<<<<<<<<<<< Here

The compiler was just getting a little overzealous in terms of checking
event method calls.

<< but also because it doesn't appear to be doing what it should, or at
least I can't work out how to get it to.  >>

See my reply to your email.

Tim Young
Elevate Software
www.elevatesoft.com

Wed, Mar 13 2013 6:19 PMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Matthew,

Actually, this might be useful for others, so here's my response about your
project issues with RemObjects:

<< In the EWB project, the CreateConnection procedure assigns a function:
   SDK.ClientChannel.onLoginNeeded := OnServerLoginNeeded;

This fails at run-time with "Uncaught ReferenceError: ClientChannel is not
defined" in Chrome. The javascript output is:

ClientChannel.onLoginNeeded = function(acallback) {
unit1_tform1.$p.tform1_onserverloginneeded.call($t, acallback); }; >>

This is the problem:

 external TClientChannel = class
 public
   constructor Create(const strUrl: string);
   property url: string read;
   class property onLoginNeeded : TOnLoginNeededCallback read write;
<<<<<<<<<<<<<< Wrong !
 end;

You're defining the OnLoginNeeded property as a class property, when it
should be a normal property:

 external TClientChannel = class
 public
   constructor Create(const strUrl: string);
   property url: string read;
   property onLoginNeeded : TOnLoginNeededCallback read write;
 end;

In addition, you're not assigning this property correctly:

   SDK.ClientChannel.onLoginNeeded := OnServerLoginNeeded; // FAILS with
"Incaught ReferenceError: ClientChannel is not defined"

should be:

   FChannel.onLoginNeeded := OnServerLoginNeeded; // FAILS with "Incaught
ReferenceError: ClientChannel is not defined"

SDK.ClientChannel is only used as a class reference for when you need to
create generic client channels, like this:

   FChannel := SDK.ClientChannel.Create(szURL);

=============================
Background information:  Technical !!!!
=============================

Any time you see a declaration in a JS class that mentions the prototype of
the class:

RemObjects.SDK.ClientChannel.prototype.xxxxxxxx
};

it means that what is being declared is a normal method/property and not a
static class method/property.  Static class methods/properties are defined
directly as part of the class in JS, like this:

RemObjects.SDK.ClientChannel.xxxxxxxx
};

So, if the OnLoginNeeded was actually a class property, it would look like
this:

RemObjects.SDK.ClientChannel.onLoginNeeded = function
onLoginNeeded(aCallback) {
   RemObjects.UTIL.showMessage("Default onLoginNeeded handler: assign
channel.onLoginNeeded and call aCallback there after successful login");
   aCallback();
};

But, you'll very rarely come across such an animal because it isn't very
useful to have callbacks that operate only in the context of a class, as
opposed to the context of a class *instance*.  In EWB, as with Delphi,
events *always* operate in the context of a class instance, and EWB *always*
automatically and transparently calls event handlers in a way that injects
the current class instance into the event handler as the current scope, or
"Self" (this in JS).  You can see this here in the output JS of your EWB app
(before fixes):

unit1_tform1.$p.tform1_createconnection = function()
{
  var $t = this, szserver;
  if ($t.tform1_fsrvc != null)
     return;
  unit1_sdk = RemObjects.SDK;
  szserver = "https://localhost/JSON";
  $t.tform1_fchannel = new unit1_sdk.HTTPClientChannel(szserver);
  $t.tform1_fmessage = new unit1_sdk.JSONMessage();
  $t.tform1_fsrvc = new Poll123Question($t.tform1_fchannel,
$t.tform1_fmessage, "Poll123Question");
  ClientChannel.onLoginNeeded = function(acallback) {
unit1_tform1.$p.tform1_onserverloginneeded.call($t, acallback); };  <<<< $t
is the current instance (Self)
};

Here is the correct compiler output, after making the corrections noted
above:

unit1_tform1.$p.tform1_createconnection = function()
{
  var $t = this, szserver;
  if ($t.tform1_fsrvc != null)
     return;
  unit1_sdk = RemObjects.SDK;
  szserver = "https://localhost/JSON";
  $t.tform1_fchannel = new unit1_sdk.HTTPClientChannel(szserver);
  $t.tform1_fmessage = new unit1_sdk.JSONMessage();
  $t.tform1_fsrvc = new Poll123Question($t.tform1_fchannel,
$t.tform1_fmessage, "Poll123Question");
  $t.tform1_fchannel.onLoginNeeded = function(acallback) {
unit1_tform1.$p.tform1_onserverloginneeded.call($t, acallback); };
};

If you have any other questions, please let me know.

Tim Young
Elevate Software
www.elevatesoft.com
Wed, Mar 13 2013 6:44 PMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Matthew,

Forget this last part:

"But, you'll very rarely come across such an animal because it isn't very
useful to have callbacks that operate only in the context of a class, as
opposed to the context of a class *instance*.  In EWB, as with Delphi,
events *always* operate in the context of a class instance, and EWB *always*
automatically and transparently calls event handlers in a way that injects
the current class instance into the event handler as the current scope, or
"Self" (this in JS).  You can see this here in the output JS of your EWB app
(before fixes):"

I'm just muddying the water there.  The scope of the event *handler* is
actually irrelevant in this situation.  The key thing is that RemObjects
expects the OnLoginNeeded event to be assigned to an *instance* of the
ClientChannel class, not as a class property of the class.  You can see this
here in their code:

RemObjects.SDK.ClientChannel.prototype.dispatch = function
dispatch(aMessage, onSuccessFunction, onErrorFunction) {
   function handleException(e) {
       if (((e.name == "EROSessionNotFound") || (e.name ==
"SessionNotFoundException")) && !(that.retrying)) {
           if (that.onLoginNeeded) {  <<<<<<<<<<<<<<<<<<<<<<<<<<<  Here,
they're referring to this (assigned as "that" below), which is the current
instance of the ClientChannel class
               that.onLoginNeeded(function() {
                   that.retrying = true;
                   that.dispatch(aMessage, function(__msg) {
                       that.retrying = false;
                       onSuccessFunction(__msg)
                   },
                           function(__msg, __e) {
                               that.retrying = false;
                               onErrorFunction(__msg, __e);
                           });
               });
           };
       } else {
//                    if (window[e.name] && window[e.name].prototype
instanceof RemObjects.SDK.ROException) {
           if (RemObjects.SDK.RTTI[e.name] &&
RemObjects.SDK.RTTI[e.name].prototype instanceof RemObjects.SDK.ROException)
{
               e = new RemObjects.SDK.RTTI[e.name](e);
               e.fields.readFrom(aMessage);
           };
           if (onErrorFunction)
               onErrorFunction(aMessage, e);
       };
   };
   var that = this;

If you have any other questions, please let me know.

Tim Young
Elevate Software
www.elevatesoft.com
Thu, Mar 14 2013 5:28 AMPermanent Link

Matthew Jones

Many thanks for that. The second part, where I was assigning it incorrectly, was
part of my fumblings trying variations to have it work. (I don't want to trouble
you without having had a good go first!). The first part, the "class" one, was the
real one with my poor understanding. And of course it works perfectly now with
those two changes.

In summary, for those using the ROSDKUtils posted by Bob here, if you want to use
the automatic session-needed login facility, you need to change this section to add
the onLoginNeeded.

 external TClientChannel = class
 public
   constructor Create(const strUrl: string);
   property url: string read;
   property onLoginNeeded : TOnLoginNeededCallback read write;
 end;

Above that, you need these lines:
 
external TOnSuccessCallbackEx = procedure (nIgnore : Integer) of object;
external TOnSuccessCallback = procedure () of object;
external TOnErrorCallback = procedure (msg: TMessage; ex: EError) of object;
external TOnLoginNeededCallback = procedure (aCallback : TOnSuccessCallbackEx) of
object;

Now, note that at this point in time, the TOnLoginNeededCallback refers to a
TOnSuccessCallbackEx parameter. This is because the current EWB has this issue with
the parameter being missing, but it works anyway. Once that is fixed, you can scrap
the Ex version and delete that.

My code that implements the login callback is this:

procedure TfrmControl.OnServerLoginNeeded(aCallBack : TOnSuccessCallbackEx);
begin
   Report('Called back');
   m_pfnLoginRetry := aCallBack;
   DoLogin;
end;

Where the m_ value is defined in the form as:
   m_pfnLoginRetry : TOnSuccessCallbackEx;

The login is:

procedure TfrmControl.DoLogin;
begin                        
   Report('Logging in');
   m_bLoggedIn := false;   // safe assumption surely?
   if FLoginSrvc = nil then
       FLoginSrvc := TLoginService.Create(FChannel, FMessage, 'LoginService');
FLoginSrvc.LoginAccount(editEmail.Text, editPassword.Text,
OnServerLoginComplete, OnLinkError);
   Report('Called Logging in');
end;

And finally, when the login response comes back you can call that callback function
to make the original call try again:

procedure TfrmControl.OnServerLoginComplete(nResult : Integer);
begin                                      
   Report('Login response ' + IntToStr(nResult));
   if nResult = 1 then
   begin
//        m_bLoggedIn := true;
       if assigned(m_pfnLoginRetry) then
       begin
           Report('Found retry fn');
           m_pfnLoginRetry(0);
           m_pfnLoginRetry := nil;
           Report('Called retry fn');
       end
       else
       begin
           Report('No retry fn');
           RunStateMachineEx(STATE_VOTEREADY);
       end;
   end
   else
   begin
SetStatus('Sorry, the account details are not recognised. Please try
again.');
   end;
end;

   
/Matthew Jones/
Thu, Mar 14 2013 12:51 PMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Matthew,

<< In summary, for those using the ROSDKUtils posted by Bob here, if you
want to use the automatic session-needed login facility, you need to change
this section to add the onLoginNeeded. >>

Thanks very much - we've got a lot of customers using RO, so it's good to
have this documented.  At some point I need to get RemObjects' permission to
include an external interface for RO in EWB.

Tim Young
Elevate Software
www.elevatesoft.com

Image