Overview

Genero Web application (GWA) is a Genero application running entirely in the browser.

The GWA is an end-to-end web application solution that bundles together a Genero Browser Client, Genero p-code modules (42m), and resources into a single web page application (SPA). The GWA build process creates WebAssembly (external link) (wasm) files for your application — a format that enables deployment on the web and execution of the application in a browser. The GWA uses the emscripten (external link) open source compiler to create the virtual machine (runtime) for the Genero p-code in the 42m modules. This results in a highly-optimized executable application that runs almost as fast on the browser as native code on the desktop, while being portable and safe.

With a GWA application, you do not need a runtime system (fglrun) process on the server side. In fact, there is no other software server side except a web server. A GWA application is by default a "progressive web app" (PWA); meaning once loaded from a web server, it can continue to run offline without any connection to the internet.

All GUI features available in the Genero Browser Client are fully supported in a GWA application, so Genero forms and styles run as usual together with Genero interaction statements such as INPUT/INPUT ARRAY/CONSTRUCT/MENU/DIALOG, and so on.

A GWA application has certain similarities with Genero Mobile for Android™(GMA) and Genero Mobile for iOS (GMI) application; however, the main difference is that the Genero runtime is not compiled into native code, instead the runtime system is compiled to WebAssembly (external link) modules (wasm) for execution on the browser.

Another noticeable difference between GMA/GMI and GWA, is that a GWA application emulates a file system in the browser, whereas in a GMA/GMI application the file systems of Android/IOS® can be used.

Prerequisites

GWA has requirements for delivering applications in a secure context (external link) as defined by the W3C consortium:
  • The GWA uses Service Workers that require you to run applications with HTTPS activated, or to use localhost. Even if in development localhost can be used, it is mainly necessary to develop with HTTPS activated. Of course, in production, HTTPS is mandatory.
  • Note:

    Self-signed certificates are not supported on all browsers.

GWA use cases

The following are examples of some common GWA use cases:

  • Run application from a URL: Build a GWA application like you would a mobile application for GMI/GMA except with the advantage that you do not have to managed it through App Store; the installation is an ordinary web URL. Once the URL is selected by the end user, the application is downloaded, starts immediately, and is also immediately available for offline use.
  • Launch from mobile home screen icon: For mobiles with Chrome/Edge browsers, an installation menu appears, prompting the end user to save this application under a mobile home screen icon. Starting the application from this icon does not require an internet connection. With other browsers, users can bookmark the application with the same offline functionality. In IOS Safari® for example, add to home screen.
  • Launch from desktop icon: For desktops with Chrome/Edge browsers, an install button appears in the address bar, which allows the user to save the application to a desktop icon, turning the application into a desktop application with offline functionality.
  • Facilitate large numbers of simultaneous end users: GWA applications are the medium of choice when it comes to giving access to large number of simultaneous end users. Standard Genero desktop applications are server based and scale well up to 1000 and even more end users, but if the potential simultaneous end user count is much higher (for example, 100,000 or more) then it is time to switch to GWA architecture – with web services server side and the program logic running entirely client side.
  • Create GWA applications written in Genero BDL: This is typically the case for a GWA application: code written entirely in Genero runs client side, communication with the server is done via Genero com API/REST services. GWA applications can therefore compete with other client-side JavaScript technologies; with the added advantage that you can code your applications in Genero BDL.

Recommendations for use of Genero Web application

GWA has these best practice recommendations:

  • Install the GWA and the REST services on the same host: As GWA is actually a web application running in a browser, you can only communicate via REST web services to exchange data with a third party server. Therefore, we recommend you install the GWA and the REST services on the same host and port; otherwise, you may face a lot of Cross-Origin Resource Sharing (CORS) issues.
  • Set CORS headers on external servers providing web services: If you need to access another REST service located on another host and port machine, and if the REST requests are basic – simple GET or x-WWW-form-urlencoded type requests – you will have to set the following CORS header in the second host machine: Access-Control-Allow-Origin:"https://host:port"

Features with limited support

You can embed a Genero application as web application with the following constraints :
  • A minimum subset of the Genero Web Services REST API is supported: the com API (excluding the specific XML API) is based on the JavaScript XMLHttpRequest object.
  • A limited number of GWS REST functions are available. When using fglrestful to generate REST stub files, only the JSON API (util.JSON) is supported. Therefore, you must use option --legacyJSONApi of the fglrestful tool. Only requests with JSON or plain text are supported. XML is not supported.
  • The RUN command is under construction to support RUN "fglrun program" (GUI only) without OS shell underneath.
  • Only the SQLite database is supported at this time.
  • All socket or pipe Channel API are not working and will return an error, whereas file Channels are supported.

Unsupported features

The following language features are not supported:

  • SOAP is not supported.
  • XML encryption API is not supported.
  • IMPORT xml, IMPORT security are not supported.
  • IMPORT json is not supported

File system

When the GWA application is launched in the browser, a virtual UNIX-like file system emulation of your GWA application is created in memory.

By default, the following directories are created when a GWA starts:
  • /app directory contains the .42m modules and program resources (with possible subdirectories).
  • /fgl contains the Genero runtime assets.
  • /tmp is for temp files.
  • /home is the default directory when the application starts. The /home is also the "persistent" directory of the GWA because it stores data even if the application is restarted (or the browser is restarted). For more information about managing persistence in the GWA, go to Managing persistence in the file system.
These are the default directories; other directories can be created with the os.Path.mkdir function as needed.

Setting environment with fglprofile

Configure environment settings with FGLPROFILE entries.

If you have a fglprofile file in the root of your program directory, it will be bundled with your application package. The GWA adopts the same behavior as GMA/GMI for setting environment. For more information, go to Setting environment variables in FGLPROFILE (mobile).

The two main environment variables you may have to set for GWA are FGLLDPATH and FGLIMAGEPATH:
  1. If you have to set FGLLDPATH because some 42m files are located on subdirectories, you must create a fglprofile with an entry called mobile.environment.FGLLDPATH
  2. You can also set other environment variables such as FGLIMAGEPATH

    In this example, environments for FGLLDPATH and FGLIMAGEPATH are set. "$FGLAPPDIR" refers to the virtual file system /app directory in memory.

    For a portable variant of your GWA application, set these environment variables as shown:
    #portable variant which works also for GMI/GMA
    mobile.environment.FGLLDPATH  = "$FGLAPPDIR/dbsync"
    mobile.environment.FGLIMAGEPATH="$FGLAPPDIR/image_dir:$FGLDIR/lib/image2font.txt"
    For a non-portable variant, or for GWA specific, set the environment variables as shown:
    
    # non portable: GWA specific
    mobile.environment.FGLLDPATH  = "/app/dbsync"
    mobile.environment.FGLIMAGEPATH  = "/app/image_dir:/fgl/lib/image2font.txt"

Managing persistence in the file system

As directories in a GWA application are in memory, this means the lifetime of those directories is the same as the lifetime of the application. Understanding the file system will help you develop a strategy for making data persistent.

Before the GWA application starts, the file system emulation is populated with data provided by gwabuildtool. For example, the "/app" directory is populated with the data from the gwabuildtool --program-dir option.

The /home directory is excluded from the pre-population of the file system; all data which can be bundled lands in /app directory.

Home directory

The /home directory is the "persistent" directory of the GWA, where the application state can be stored even if the application is restarted or the browser is restarted. Data in the /home directory is stored in the browser cache, or to be more precise, in IndexedDB (external link) caches. Therefore, if you want to store data, you must put your files in the /home directory using explicit channel operations such as os.Path.copy commands as shown in the next section.

Database persistence

Database persistence is managed via a SQLite database file, which must be placed in the /home directory.

If an SQLite database file needs to be created at application startup, you must write some 4GL code to create the file before calling the CONNECT instruction.

If your application comes with a predefined database, you must code to detect it exists at runtime, and if not present, you must copy the initial database(mydatabase.db in the example) bundled by gwabuildtool in the /app directory to the /home directory as shown:
# check for database
VAR home_db="/home/mydatabase.db"
#...
IF NOT os.Path.exists(home_db) THEN --Copy to home directory first time
  --the initial mydatabase.db is bundled by gwabuildtool into /app
  IF NOT os.Path.copy("/app/mydatabase.db", home_db) THEN
    DISPLAY "Can't copy initial db"
    EXIT PROGRAM 1
  END IF
END IF
# connect to database

Accidental clearing of the cache

Explicitly cleaning the browser cache for the site hosting a GWA application will remove all stored data of a GWA application.

As there is always the danger that an end user may clear browser cache and thus delete the contents of a GWA application /home directory unintentionally, your GWA application should be prepared to send the data to a REST server via REST API, or synchronise data with the server by other combinations of GWS calls.

Web application manifest file

A web application manifest file is a JSON file that provides information about a GWA.

The manifest has some parameters that allows you to change icons, add screenshots, change application title, and so on, according to the Web Application Manifest (external link) specification.

When you call the gwabuildtool tool to build the GWA, it looks for a dedicated manifest in the programdir/gwa of your program directory.

In the manifest file, the path to icons and screenshots are set by the "src" property. "src" does not refer to a web server path, instead gwabuildtool searches for those images in the absolute path or a path that is relative to your programdir/gwa path.

So an image named "src" : "foo.png" will be found in programdir/gwa/foo.png. This is the recommended default directory for those kind of images.

It is recommended that you always place icons and screenshots relative to the programdir/gwa path, even if the following is possible:
  • An image named "/Users/bar/foo.png" will be found in /Users/bar/foo.png.
  • An image named "../foo.png" will be found in programdir.

Packaging web components

Web components must be packaged with the GWA application because they are preloaded and must also be available offline.

The directory programdir/webcomponents is the default location for web components in your program directory. All components located there will be automatically packaged.

Furthermore, the gwabuildtool has a --webcomponent switch to bundle a web component from another location in the file system other than programdir/webcomponents. The switch can be used multiple times in the build command.

Including built-in web components

The gwabuildtool also scans all form files (.42f) in your application and checks them for the COMPONENTTYPE attribute. This affects the build in the following way:
  1. It adds the specified Built-in web components from $FGLDIR/webcomponents to the build. For example, if a COMPONENTTYPE matches "fglsvgcanvas", the fglsvgcanvas web component is automatically included in the GWA.
  2. It raises a warning if, for a particular COMPONENTTYPE, no web component has been packaged.

A call to ui.Interface.filenameToURI(asset_in_filesystem) can be used to produce URLs to make a web component visible.

Note: Identifying web component URL paths

Notice that URL paths starting with "webcomponent" refer to files bundled in the programdir/webcomponents subdirectory and URLs starting with HTTP(S) can refer to assets in the internet.

The gwa_webcos demo is a good source to check the various asset paths.

Application updates

A GWA API queries the status of the application and can update it automatically even when not active.

When a GWA application ends, an application ended page is displayed similar to GBC. (This page can be customized). By default a "Reload page" button is shown on this page, which enables a restart of the application.

If the application has been updated server side in the meantime, clicking the Reload page button will start the updated version of the application.

Automatic application updates

As the GWA uses JavaScript service workers, the application can even be updated when the program is not active; the browser automatically checks the server side for a new version and downloads the new version if there is one.

By default the application is also updated automatically when it is reloaded. This can happen when the user clicks the Reload page button on the application ended page, by using the browser reload menu action (F5 usually), or by reopening the browser with the same application URL.

To keep track of application updates, the gwabuildtool has an --app-version switch to set the application version. This version can be queried by using methods in the gwa.app package module.

User-driven update

The gwa.app package module has a function to query the up-to-date status of the application and perform a user-driven update immediately if a version difference is detected.

If a network connection is not available, the current installed version in the browser is used. Internally, the GWA tries first to contact the server for an update, if this fails, after 500ms the cached application version is used.

It is recommended that you code to check for updates after your application starts by calling the gwa.app.getServerVersionAndBuild() method.

For an example performing a user-driven update, explore the GWA "update" demo in your GWA installation directory. If installed in FGLDIR, you will find the demo in $FGLDIR/demo/gwa/update or if installed in a separate directory, you will find it in gwa-install-dir/demo/gwa/update.

Single sign-on (SSO)

Single Sign-On (SSO) allows users to sign in through an Identity Provider (IdP) using one set of credentials and access multiple applications, such as a Genero Web application. Enabling SSO increases security and makes it easier for users to sign in to your GWA application.

Genero provides SSO based on the OpenID Connect (OIDC) protocol via its Genero Identity Provider (GIP). For more information on GIP and SSO, refer to the Single Sign-On User Guide. Any identity provider that supports OIDC will work with GWA and can manage the creation of user accounts as well as authentication and authorization during sign-in.

Overview

When your GWA application is registered with an IdP for SSO, the end-user enters credentials once in a login form on the browser to get authorization to use the application. If your application performs further request to REST services, an access token is required. Your application must perform a request via a password-credential flow to the IdP in order to receive an authorization and an access token for this purpose.

CORS constraints

If the IdP is on a different server to the GWA, due to Cross-Origin Resource Sharing (CORS) issues, you must add the Access-Control-Allow-Origin HTTP header on the other server in order to authorize GWA to contact the IdP on a different host. The header must provide the hostname and port number of the server where the IdP is located. For example, if you IdP runs on a server called "cube", then add the following header:
Access-Control-Allow-Origin:"https://cube:6394"
This can be achieved on the GAS by setting the SERVICE (for HTTP) entry of the as.xcf with the server address:
#...
<SERVICE>
    <HEADER Name="Access-Control-Allow-Origin">https://cube:6394</HEADER>
</SERVICE>
#...

OAuth API

In your GWA application, you must call functions of the OAuthAPI library in order to authenticate and retrieve an access token to call additional REST services. See the example in Typical BDL OAuth initialization.

Of course, you can also use fglrestful --auth yes to generate the stubs to connect using OAuth protocol to any REST services. However, as GWA has a limited number of GWS functions available at this time, you must use option --legacyJSONApi of the fglrestful tool, and only requests with JSON or plain text are supported. XML is not supported.

Typical BDL OAuth initialization

In this sample, there are examples of the OAuthAPI calls you must make in your GWA application in order to authenticate and get an access token to call REST services. You initialize the SSO user identification with OAuthAPI:
IMPORT FGL OAuthAPI

-- IDP constants
CONSTANT idp_issuer = "https://host:port/gas/ws/r/services/GeneroIdentityProvider"
CONSTANT client_id = "XXXXX" -- OAuth client id as registered in your IdP
CONSTANT private_id = "ZZZZZ" -- OAuth private id as registered in your IdP

-- OAuthAPI
DEFINE metadata OAuthAPI.OpenIDMetadataType
DEFINE tokens OAuthAPI.OpenIdCResponseType
DEFINE user_login, user_pswd STRING
DEFINE s INTEGER
DEFINE r BOOLEAN

MAIN
  -- Initialize user_login and user_pswd (from a login dialog for example)
  -- CALL AskForLoginAndPassword() RETURNING s, user_login, user_pswd
  IF s<0 THEN EXIT PROGRAM 0 END IF

  -- Fetch IDP metadata
  CALL OAuthAPI.FetchOpenIDMetadata(5, idp_issuer)
     RETURNING metadata
  IF metadata.issuer IS NULL THEN
    DISPLAY "ERROR: Could not fetch OAuthAPI.OpenIDMetadataType"
    EXIT PROGRAM 1
  END IF

  -- Perform password credential flow 
  CALL OAuthAPI.RetrievePasswordTokenForNativeApp(5, metadata.token_endpoint,
                                                user_login, user_pswd,
                                                client_id,
                                                private_id,
                                                NULL)
     RETURNING tokens 
  -- Initialize OAuthAPI via returned tokens
  LET r = OAuthAPI.InitNativeApp(5, tokens,
                               client_id,
                               private_id,
                               metadata.token_endpoint)
  IF NOT r THEN
    DISPLAY "ERROR: Could not initialize native app with OAuthAPI.InitNativeApp"
    EXIT PROGRAM 1
  END IF
  -- Use any GWS client stub generated with fglrestful --oauth yes --legacyJSONApi
END MAIN

Front calls with runOnServer

The runOnServer front call allows you to start an application via the Genero Application Server (GAS) from a Genero Web application (GWA).

A runOnServer call is basically implemented as described for Genero Mobile for iOS (GMI) and Genero Mobile for Android (GMA) applications in mobile.runOnServer; however, there are two notable differences or things to consider when implementing runOnServer calls in a Genero Web application:

  1. In GWA, unlike for GMI/GMA, both RUN and RUN ... WITHOUT WAITING can be used in any combination.

    If we wanted to limit it to a RUN, the GBC would need to know if it is in a runOnServer state.

    The runOnServer application finishes when the GBC loaded with the remote application detects application termination (meaning the session ended).

  2. In the GWA, you can omit the full origin (http://host:port) in the GAS URL argument, because the origin of the starting GWA application and the GAS application must be the same. (A Cross-Origin Resource Sharing (CORS) issue could arise, but this would require a modification in the GBC.) Therefore, the call for the GAS URL argument can be reduced to a simple path like /ua/r/simple. In GMI/GMA, this is not possible because the native application always runs on another origin.