Skip to Content
Technical Articles
Author's profile photo Thales Batista

Short Circuit Fiori Launchpad bits: Turn it into a daft WebApp (PWA Movement)

Fiori Client removal from app stores was a buzz few months ago, big enough to make SAP rethink about its phasing out plan: postpone removal date, change distribution channel from (manual) ONE Support Downloads into a Managed distribution through stores (some business steps involved, but easier easier for common employees install on device).

Wouldn’t be nice if they embraced Progressive Web App philosophy into Launchpad roots? They sell as Fiori apps, why not take it seriously as an app platform?

While you and me daydream about it, let’s toy around what we have today.

Disclaimers

Few days before publishing I saw this answer from Gregor Wolf mentioning a video from an earlier UI5Con. I saw it quickly (2x speed) and looks partially to what I tell here.

That video also focus on a proper Service Worker; something that I stub here for a reason also mentioned on video: there are quirks to use it on Launchpad. The main one for me, at development view, is being hostage of Framework/Vendor: there is no public documentation / API for Cache Buster tokens and (at the time of video) some UI5 requests were doing synchronous calls that bypass Service Worker. You can reverse-engineer the standard and implement into a functional state. It is unlikely to break between upgrades but the risk exists.

Sorry about some mobile screenshots taxing your scroll, blog engine sanitize any max-width/max-height attempt.

Chrome doesn’t provide a way to manually change its display language on Linux (only ABAP sandbox have English as OS language), so I took the freedom to take Mobile screenshots in Portuguese too (silent protest against poor translation quality in some areas of Solution Manager).

A (very) brief intro to PWA

Read this page.

What? It’s brief to me, three words. That’s not cheating, it’s reuse. Who already heard about this will thank me for not wasting their time.

Ok, I will extend a bit: PWA is a way to make Web Applications (Web pages)  have a “native application” feeling to end-users, and one of those things is allowing them to install it (aka. Add to Home Screen, A2HS), the main focus today.

Requirements to PWA be installable

Read this other page or this one.

Stop with this laziness man!

Seriously, read them. There are common requirements for all browsers, but each one can have a different requirement, because PWA is still a draft specification, even seven years after its first public draft.

Quick story short: to enable installation on Chrome, Firefox and Edge you must have two files declared in HTML: a Web Application Manifest and a JavaScript to register a Service Worker. Service Worker operates in a separate file, you’ll also have to write it and serve it too.

Functional stubs (at least on time of publication) for each resource are provided below for your laziness:
Web Application Manifest

{
    "name": "Bonder Lostpad",
    "short_name": "BLP",
    "icons": [
        {
            "src": "/pwa/icon192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "/pwa/icon512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ],
    "theme_color": "#ffffff",
    "background_color": "#000000",
    "display": "standalone",
    "scope": "/",
    "start_url": "/"
}

JavaScript to register a Service Worker

if('serviceWorker' in navigator) {
    navigator.serviceWorker
        .register('/pwa/sw.js', {scope: '/'})
        .then(function(registration) {
            console.log('Service Worker Registered with scope:',  registration.scope);
        });
}

Service Worker itself

self.addEventListener('fetch', function(e) {
  // Hi, I'm Stubo!
});

thdtW2G8alTTw

Top tip: The easiest way to check if requirements to install are fulfilled is using Google Lighthouse, it pinpoints what is missing and what has error. This example, for example, will stop working in Chrome later in 2021, because it will be mandatory to have a offline mode.

Lightouse%20results%20about%20our%20Launchpad%20enhanced%20with%20those%20stubs.

Lighthouse results about our Launchpad enhanced with those stubs.

Now you know that the main focus to enable it lies in HTML and JavaScript running on it. Time to inspect Fiori Launchpad.

Bird’s-eye view onto Launchpad resources: identify which HTML pages are loaded

To know where to inject files we need to know what HTML pages make part of Launchpad experience. Developer console will aid you, it’s just a filter away.

Developer%20console%20tracing%20HTTP%20requests%2C%20filtered%20by%20Document%20%28HTML%29

Developer console tracing HTTP requests, filtered by Document (HTML).

Two HTML pages are identified: Login Screen and Launchpad itself. Too easy, I didn’t even sweat.

Non-invasive approach: Injection on HTTP response

The concept is very simple: ABAP system keeps untouched, we hijack some HTTP requests and inject PWA-related things into response, like a man-in-the-middle attack. Actually it is exactly like that and is legal: a Reverse Proxy doing content rewrite.

The one I used was Nginx, but you should be OK with any other like Apache. The needed features here are Content Rewrite and serve static files (for PWA specific resources).

Put the rules, spawn service and good to go!

# Only relevant bits of nginx config.
# It could be better written...
location  /pwa/ {
    alias /var/pwa_stuff/pwa/;
}
location = /pwa/sw.js {
    types { } default_type "text/javascript";
    add_header "Service-Worker-Allowed" "/";
    alias /var/pwa_stuff/pwa/sw.js;
}
location = / {
    proxy_pass https://system.somewhere/sap/bc/ui2/flp;
    # PWA subfilter: Inject the stuff
    sub_filter_once off;
    sub_filter '<head>' '<head><link rel="manifest" href="/pwa/manifest.webmanifest">';
    sub_filter '</body>' '<script type="text/javascript" src="/pwa/app.js"></script></body>';
    sub_filter '/sap/bc/ui5_ui5/ui2/ushell/shells/abap/FioriLaunchpad.html' '/';
}

Install%20prompt%20for%20Bonder%20Lostpad%20in%20desktop.

Install prompt for Bonder Lostpad in desktop.

Install%20banner%20on%20mobile%20and%20also%20highlighting%20install%20option%20on%20browser%20menu.

Install banner on mobile and also highlighting install option on browser menu.

Being installable, you can also have shortcuts for specific tasks (Fiori apps). Don’t expect too much, it is less flexible than native app shortcuts.

PWA%20shortcuts%20for%20Solution%20Manager%20specific%20apps.

PWA shortcuts for Solution Manager specific apps.

Celebration tonight, celebrate, don’t wait too late…

“Aw man, you can’t stop now. This will not fulfill your blogging quota,” ten minutes rule said.

“But we’re just going to celebrate,” said me. “Can I Crescendolls ipsum it?” I thought.

Shoot, I forgot why I wanted to blog this. That’s what happens when you stop to listen some robots rocking (those aren’t the ones you are thinking of ). Let’s get back to the program.

One more time, ABAP approach

Many ABAP landscapes have a kind of Reverse Proxy running called SAP Web Dispatcher. I don’t recall any Rewrite Content feature on it (I could be wrong, it’s not my expertise), but still this software is handled as a sensitive part of Landscape and is out of bounds for a common Developer, the brave one picked in the crowd to make things like this work. Let’s try another approach: patch that in a developer way, there’s nothing wrong with just a little fun.

We saw that two HTML pages were loaded. Login one isn’t really needed to address, because it only makes sense to allow installation for users logged in.

Launchpad Page: Cimico Quo

From the other developer folk tale you should now know how to find the link between Fiori and ABAP, and this one is no different.

Mapping%20of%20developer%20console%20HTTP%20request%20and%20SICF%20handler%20implementation.

Mapping of developer console HTTP request and SICF handler implementation.

After some effort voyaging around the code you’ll find where SAP render Fiori Launchpad page, precisely at some bits of /UI2/CL_FLP_HTTP_HANDLER class. They even do some content injection on template.

A%20nice%20spot%20to%20implicit%20enhancement%20would%20be%20inside%20of%20inject_config_metatags%20method%2C%20where%20standard%20does%20content%20injection%20into%20Fiori%20HTML%20page.

A nice spot to implicit enhancement would be inside of inject_config_metatags method, where standard does content injection into Fiori HTML page.

We’ll take advantage of that aerodynamic part and buff it with an implicit enhancement, our injection.

constants pwa_resources_path type string value `/sap/public/zflp_pwa_res`.
data(content) = ev_html_with_config.
content = replace( val = content sub = `</head>` with = |<link rel="manifest" href="{ pwa_resources_path }/manifest.webmanifest"> </head>| occ = 1 ).
content = replace( val = content sub = `</body>` with = |<script type="text/javascript" src="{ pwa_resources_path }/app.js" async></script></body>| occ = 1 ).
ev_html_with_config = content.

Refreshing Launchpad you’ll get network errors, as expected, because two new resources were pointed (manifest.webmanifest and app.js) in HTML but ABAP server doesn’t know them, yet.

Serving static content: High Life on Public node

You can try but you won’t be able to directly create a PUBLIC child node. No worries about that, KBA 2478325 explains a bugnot-so-trivial feature to accomplish this (yes, you could avoid a bit of this creating as a child of an existing leaf, but you would lose all the fun).

Why am I serving from a public node? Nothing special. My first experiments had Login page as part of PWA experience, and then you have to go public (No-user user in action).

A node needs a handler class, time to implement it. I recommend using the MIME repository to store PWA files, because you get a nice API to get it back, either on code (it’s amazing what you’ll find sneaking around API word), or codeless if you know the drill (read documentation).

thdtW2G8alTTw

method if_http_extension~handle_request.
  check server->request->get_method( ) eq if_http_request=>co_request_method_get.
  data(path) = server->request->get_header_field( if_http_header_fields_sap=>path_info ).

  case path.
    when `/app.js`
      or `/manifest.webmanifest`
      or `/sw.js`
      or `/icon192.png` or `/icon512.png`.

      " Expected resources for PWA. You shall pass.
    when others.
      " Someone meddling with service. Mock them!
      server->response->set_cdata( `Ah ah ah! You didn't say the magic word!` ).
      server->response->set_status( code = 400 reason = if_http_status=>reason_400 ).
      return.
  endcase.

  data(mime_api) = cl_mime_repository_api=>get_api( ).
  mime_api->get(
    exporting
      i_url = |/SAP/PUBLIC/ZFLP_PWA_MIME{ path }|
      i_check_authority = abap_false " "No-user" user is used when not logged in, he has no authorizations.
    importing
      e_content = data(content)
      e_mime_type = data(content_type) ##type
    exceptions
      others = 0 ).
  server->response->set_data( content ).
  server->response->set_content_type( content_type ).

endmethod.

This is enough. Just load Web Manifest and Service Worker files into MIME and it is ready for the stage.

Can you see it? Oh can you see it

Install%20prompt%20directly%20from%20our%20ABAP%20changes.

Install prompt directly from our ABAP changes.

Mobile%20install%20banner%20from%20our%20ABAP%20efforts.

Mobile install banner from our ABAP efforts.

Mobile%20homescreen%20after%20installing%20both%20cases.

Mobile homescreen after installing both cases.

The aftermath

You could also take both experience and knowledge to a next level, like fiorize installation experience through a Launchpad Plugin. You can toy with other things to improve PWA experience, but you’ll always have to think twice if that requires something that relies on ABAP side: “Am I jeopardizing myself doing that?” “Is that code released for customers?” “Is there a public API for that?” You’ll have to develop, mend, maintain, check on every support package upgrade.

Want the better experience? Be picky and make your company expenses worth by influencing the software, or meet us on some shady corner mumbling about immutable decisions took inside.

As said earlier, PWA is still a draft specification. I wanted to have a different set of shortcuts per user role, but that is not possible now.

A git repo thalesvb/flponprem-pwa is available for your joy. Ready-to-go, one BAdI implementation away to activate this feature, bundled with one example.

Our work is never over, but this blog is. Merci à vous et à bientôt!

 

Meta-task: All tracks of Discovery album were referenced in this post, some of them more than once. Why don’t you play the game? (this one doesn’t count).

Assigned Tags

      2 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Martin Ceronio
      Martin Ceronio

      Brilliant, just what I was looking for. I need to implement a Web App manifest link in FioriLaunchpad.html as it is the only way I have managed to get the Launchpad in full screen on Android with Chrome.

      Fortunately Gregor Wolf pointed me to this post of yours.

      Thank you very much!

      Author's profile photo Martin Ceronio
      Martin Ceronio

      Hello Thales, not even a month after your blog post, the page you referred to that specified the criteria for offline mode for a PWA was updated with a notice that reads as follows:

      "Updated April 14th, 2021: We previously announced plans to update the installability criteria to ensure a PWA actually provides an offline experience. After listening to your feedback, and discovering a number of issues, we have decided to put those plans on hold. We strongly believe providing a valid page when the user is offline is critical to providing a good user experience."

      So, thankfully, (for now) that is not a requirement.

      Fortunately, providing an offline experience does not necessarily mean providing a fully functioning app. It could just be a custom fallback page, as they point out.