Categories
The Power of PSEvent

PSEvent Targeted Logout

A while back, I did a presentation for the Appsian Innovation Summit Winter 2021 called The Power Behind the PeopleSoft Push Notification Framework.

It showed you how I had used the PSEvent object with my own PeopleSoft Broadcasting Component to broadcast messages to users, send a message to users with a timer countdown, find out what users are on the system at this moment in time, query information about what a specific user is up to at this moment or, as we are going to discuss today, the targeted logout of a specific user from PeopleSoft PIA.

So lets take a look at the PSEvent class and what it can do. It is an abstraction of Websocket, this means that PeopleSoft makes it really simple for us to use Websockets. So what is a Websocket and what makes it so great?

Websockets are a communication protocol just like Http or AJAX. With Http you have a request response model where the client opens a connection, sends out a synchronous request, say a web address and the response is sent from the server such as an HTML page and that is the end of the connection, the server no longer knows anything about the client.

With websockets you establish a trusted connection between the client (web browser) and webserver and that connection persists until closed. Because websockets are a persistent connection it means that the communication is a lot quicker than HTTP, because every time you want to send a HTTP request you have to set up a connection and send the communication headers.

So when a user signs in to PeopleSoft PIA a Websocket connection is setup between that user’s browser and the webserver. This connection is what we are going to exploit, because we can travel along these connections from the webserver to a specific user.

So to access these connections we need to create an event by navigating to PeopleTools > Push Notifications > Define Server Events. Think of the event object as similar to an Integration Broker message, it’s an object that can carry whatever data you define. You can define the content of an event as either rowset data or keyvalue pairs. I personally prefer keyvalue pairs as this is the form JSON uses, which is a lot more universal especially when you will be subscribing to these events with JavaScript, which knows nothing of a PeopleSoft Rowset.

I have created EDGPBCMSG1 (Edge PeopleSoft Broadcasting Component Message 1!) and it has 4 keyvalue pairs.

1 – MSGTYPE will indicate the type of message, so that I can use the same event but send different messages. In my full presentation this message sent the broadcast event, timer event, user count, check user and logout event all from the one PSEvent definition.

2 – PAYLOAD is a generic field for holding whatever data might need to be transferred to the target. In the logout it does not need a value, we simply want to logout the user receiving the event.

3 – TIMER is another field not used by the logout event type, it is used when sending a timer message (another example from the presentation).

4 – REQUESTER passes the Userid of the person triggering the event.

So now we need to write some peoplecode to publish our event. That is really straight forward. I put some code on a button field change.


Local object &EventObject;

&EventObject = CreateObject("PSEvent");

   First up we need to initialise our PSEvent, we do this by declaring a variable of type ‘object’ and then instantiating that with the CreateObject function into an object of type ‘PSEvent’.

Evaluate GetLevel0()(1).EDG_PBC_WRK.EDG_PBC_MSGTYPE.Value
When = "OUT"
   &EventObject.SetKeyValuePair("MSGTYPE", GetLevel0()(1).EDG_PBC_WRK.EDG_PBC_MSGTYPE.Value);
   &EventObject.SetKeyValuePair("PAYLOAD", "");
   &EventObject.SetKeyValuePair("TIMER", 0);
   &EventObject.SetKeyValuePair("REQUESTER", %OperatorId);
   &EventObject.AddRecipient(GetLevel0()(1).EDG_PBC_WRK.OPRID.Value, 1);
   Break;

The code above is then responsible for populating my PSEvent object that I have created. You may notice that even at this point when we are adding data to the PSEvent object, we have not said which of our defined PSEvents we are using, therefore the PSEvent object does not know what data to expect, so we could actually add all sorts of data to the object, it is only upon the publish that we confirm with PSEvent message we wish to use. So bear this in mind that it is up to you to ensure that you add the correct data required by your PSEvent. The code here has a field ‘EDG_PBC_MSGTYPE which is a dropdown on the page which I select to choose which message to send. In the evaluate statement you can see that a MSGTYPE of ‘OUT’ is what I use to identify my logout event trigger. The next 4 line use the ‘SetKeyValuePair’ method to add the data to the PSEvent, with the parameters simply being ‘Key’ and ‘Value’ (example key = ‘REQUESTOR’, value = ‘%OperatorId’). The final method call is the AddRecipient method which tells PeopleSoft who I want to sent the message to. It takes 2 parameters, the target and then a mode. The mode of 1 means send it to a user, whereas a value of 2 means send it to all users with a given role, while setting no recipients means that everyone gets the event!

So you can see by using this method we could logout every user in the system or certain groups using the roles or just single people as in my example. There is a sister method of AddRecipients which works exactly the same, except it allows you to specifiy multiple users or multiple roles to make this even more flexible.

&EventObject.Publish("EDGPBCMSG1");

Finally after creating our PSEvent object, populating it with data we just need to send the event to anyone listening. This is done by invoking the Publish() method of the PSEvent object and in the parameters we pass the name of the evnt that we want to send. As you can see in the code above, we are publishing our EDGPBCMSG1 that we defined on the Server Events page at the start. And that is a simple as it gets for publishing a PSEvent. Next we need to take on the subscription.

For the subscription we are going to use JavaScript rather than PeopleCode as we want the code to exist wherever the user might be in PeopleSoft. So we will put the JavaScript code in an HTML object and to ensure that the subscription code is fired I took a little hint from Sasank Vemana that he showed in one of his blogs and I added the following include statement to the bottom of the delivered PT_UTIL HTML object.

/* EDG_PBC BEGIN by R.Swailes Custom Javascript */
%include(EDG_PBC_EVT_SUBSCRIBE);

/* EDG_PBC END */

Next up we need the code that is going to go in that custom HTML object EDG_PBC_EVT_SUBSCRIBE. This will be the code that subscribes to our PSEvent and then carries out the logout command.

Subscribe("EDGPBCMSG1", OnPBCEvent);

And here is the JavaScript function Subscribe creates a listener for any PSEvents of a given type. The first parameter of the function is the PSEvent type, here you can see the name of the Server Event we defined at the start ‘EDGPBCMSG1’. The second parameter is a callback function name, what this means is you put the name of a JavaScript function that will be triggered when the specified PSEvent is sent to you. My function is called ‘OnPBCEvent’, so lets look at that JavaScript function now.

function OnPBCEvent(EventName, EventData) {
    if (EventName == "EDGPBCMSG1") {
        var mainUrl;
        var parts;
        var logoutURL;
        var xhr;
  

The first part of the function declaration, you can see that the callback function is passed two parameters, EventName which is the name of the PSEvent sent (in our case we expect this to be EDGPBCMSG1. The second parameter EventData carries the data be it rowset or in our case KeyValue pairs that we added to the PSEvent when sending it. The first thing we do is check that we have recieved the correct PSEvent by seeing if EventName = “EDGPBCMSG1”. Then we set some variables to be used later.

      var sMsgType = decodeURIComponent(EventData.Map["MSGTYPE"]);
        var sPayload = decodeURIComponent(EventData.Map["PAYLOAD"]);
        var iTimer = decodeURIComponent(EventData.Map["TIMER"]);
        var sRequester = decodeURIComponent(EventData.Map["REQUESTER"]);

Now we know we have the correct PSEvent, we are going to extract all the data send on that event. If you can remember back to the start we set our PSEvent object as having 4 data keyvalue pairs: MSGTYPE, PAYLOAD, TIMER and REQUESTER. We use the JavaScript function decodeURIComponent to ensure that any special characters that may have been encoded are decoded. EventData was the parameter holding all the PSEvent data so we use the javaScript method Map which stores the value of the keyvalue pair into a variable.


        /* Process each message type differently */
        switch (sMsgType) {
            case 'OUT':
                // User logout
                mainUrl = window.location.href;	
	        parts = mainUrl.match(/ps[pc]\/(.+?)(?:_\d)*?\/(.+?)\/(.+?)\/[chswq]\//);	
	  

We have a switch statement which is the same as an evaluate in PeopleCode. This is because my PSEvent can be used to trigger different code depending on the type. In this case if the MSGTYPE is set to ‘OUT’ then we are going to do the logout code. Next we get the url from the browser and then break this into parts, you will find this code used in many places in PeopleSoft it is the JavaScript way of doing a GenerateComponentPortalUrl.

      var logoutURL = window.location.origin + "/psc/" + parts[1] + "/" + parts[2] + "/" + parts[3] +"/s/WEBLIB_EDGPBC.ISCRIPT1.FieldFormula.IScript_edgTargetedLogout";

Now we use those parts of the browser url to create an iScript url. Although technically you can do a logout just by redirecting to a hardcoded url. I like to go to the IScript to retrieve my logout url. Here you can see I am calling the IScript_edgTargetedLogout. We will look at that IScript once we finish this JavaScript function.

       // fetch the iscript Url, use write to send a response, get response object text  and that should be the logout url
                fetch(logoutURL)
                  .then(response => response.text())
                  .then(data => { window.top.location = data;});


                break; 
         
        };
    };
};

Finally we use the generated logoutUrl to call the Iscript. We use the brilliant JavaScript fetch command. For more information on the fetch command see my EIS 2021 presentation The Power and Flexibility of JavaScript – The Understated PeopleTool. What we need to know is that the fetch call will cause the IScript to execute and we should get returned from that a response containing the logout url which will be in the variable data, which we then assign to the browser location, thus forcing the logout. So the full JavaScript for the HTML object EDG_PBC_EVT_SUBSCRIBE is below

function OnPBCEvent(EventName, EventData) {
    if (EventName == "EDGPBCMSG1") {
        var mainUrl;
        var parts;
        var logoutURL;
        var xhr;
        var sMsgType = decodeURIComponent(EventData.Map["MSGTYPE"]);
        var sPayload = decodeURIComponent(EventData.Map["PAYLOAD"]);
        var iTimer = decodeURIComponent(EventData.Map["TIMER"]);
        var sRequester = decodeURIComponent(EventData.Map["REQUESTER"]);
        /* Process each message type differently */
        switch (sMsgType) {
            case 'OUT':
                // User logout
                mainUrl = window.location.href;	
	        parts = mainUrl.match(/ps[pc]\/(.+?)(?:_\d)*?\/(.+?)\/(.+?)\/[chswq]\//);	
	        var logoutURL = window.location.origin + "/psc/" + parts[1] + "/" + parts[2] + "/" + parts[3] +"/s/WEBLIB_EDGPBC.ISCRIPT1.FieldFormula.IScript_edgTargetedLogout";
              
                // fetch the iscript Url, use write to send a response, get response object text  and that should be the logout url
                fetch(logoutURL)
                  .then(response => response.text())
                  .then(data => { window.top.location = data;});

                break; 
         
        };
    };
};

Subscribe("EDGPBCMSG1", OnPBCEvent);

So now lets take a look at the IScript that is being called and how it generates the logout url needed.

Function IScript_edgTargetedLogout
   /* Log out user */
   Local EDG_UTILITIES:EDG_User_Utilities &oEDGUtilities;
   
   &oEDGUtilities = create EDG_UTILITIES:EDG_User_Utilities();
   
   /* Set the content type */
   %Response.SetContentType("text/html;");
   
   /* Send the response */
   %Response.Write(EncodeURL(&oEDGUtilities.LogOutURL()));
   
End-Function;

I have created an App Class EDG_User_Utilities which will provide the generated logout URL, we will see that code next, but for now when we get that logout URL I set the response to text/html and then do a %Response.Write with the encoded logout url. This is just like doing a return in a function except here we are returning the code from the server IScript to the Client JavaScript.

method LogOutURL
   /+ Returns String +/
   Local string &linkUrl = "";
   Local boolean &hasaccess;
   Local integer &pos1, &pos2, &pos3, &i;
   Local string &siteName, &char, &parseSiteName;
   
   &linkUrl = %Request.LogoutURL;
   
   /* Code taken from PTBR_BRANDING.SystemElements.SignOutLink */
   
   /* Remove session number (_nn) from the URL */
   
   &pos1 = (Find("/psp/", &linkUrl) + 5);
   &pos2 = Find("/", &linkUrl, &pos1);
   
   &siteName = Substring(&linkUrl, &pos1, &pos2 - &pos1);
   
   &pos3 = 0;
   For &i = 1 To Len(&siteName)
      &char = Substring(&siteName, &i, 1);
      If &char = "_" Then
         &pos3 = &i;
      End-If;
   End-For;
   
   
   If (&pos3 <> 0) Then
      &parseSiteName = Substring(&siteName, &pos3 + 1, Len(&siteName) - &pos3);
      If IsNumber(&parseSiteName) Then
         &siteName = Substring(&siteName, 1, &pos3 - 1);
      End-If;
   End-If;
   
   &linkUrl = Substring(&linkUrl, 0, &pos1 - 1) | &siteName | Substring(&linkUrl, &pos2, Len(&linkUrl) - (&pos2 - 1));
   
   Return &linkUrl;
   
end-method;

Above you can see my LogoutURL method taken from my EDG_User_Utilities Application Class. As you can see I took this code from the PeopleSoft delivered code PTBR_BRANDING.SystemElements.SignOutLink.

FINAL NOTE: Apologies I have not had chance to create a project with the objects in yet. When I experiment with these ideas I tend to code quite messy as I hit dead ends and have to try many approaches to get something working. The code for this logout is sat in a project that includes all the PeopleSoft Broadcast Component Code, as well as my EDGE toolbar and countless utilities that I have built to make life easier. To release such a large project, I worry will confuse people if I have not had time to explain my concepts. I will release the project in the future, it just needs a little cleaning first. The problem I have personally is I am very much an ever moving forwardperson, onto the next big idea and its a struggle to stop looking at new ideas to revisit old ideas. I’ll get there with the right amount of public pressure. ?All the best.