Reference13r1:Concept Talking to the v13 Application Platform using PHP

From innovaphone wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
There are also other versions of this article available: Reference13r1 (this version) | Reference13r2

Deprecation note:

This article describes methods for connecting to the PBX as a user with the myApps protocol, that is not recommended. Instead automation scripts and external applications should connect to both the PBX and app services using the AppWebsocket protocol with an app object account.

An updated version of this article is available in Reference13r2:Concept Talking to the v13 Application Platform using PHP.

Protocol details can be found in the SDK, see


Applies To

This information applies to

  • innovaphone platform running the v13 App Platform
  • PBX running v13 firmware
  • PHP script accessing the App Services available on the App Platform

More Information

Problem Details

The v13 App Platform runs various App Services providing services which are used by Apps running in the myApps client context. Apps (written in JavaScript) are loaded from the App Platform and launched by the myApps client. They communicate to the App Services using a JSON based WebSocket protocol. Apps may also be loaded from the PBX (so called PBX Apps).

The vanilla way to go when it comes to 3rd party integration is to write a custom App Service and install it on the App Platform. Creation of such App Services is facilitated by the v13 SDK.

App-platform-php-appplatform-simplified.png

However, in some scenarios you may want to talk to existing App Services from your own application directly (that is, not by virtue of an App running in the myApps client). This article is on how to do that. General considerations are given which are true for all programming languages, some sample code is given in PHP.

App-platform-php-appplatform-simplified-3rd-party.png

Authentication

To talk to an App Service, you need to be logged in to the PBX. An App does not need to care about it, as the myApps client (which is the context all Apps run in) would take care of this.

The process is as follows:

  • the application must log-in to the PBX
    • depending on the configuration of the PBX, the authentication type can be digest (the PBX itself knows the user credentials) or ntlm (the PBX passes the user credentials to a 3rd party authentication service using netlogon)
    • depending on the configuration of the PBX, two factor authentication may be required. In this case, the calling application must be prepared that the login process may take a long time (the time needed to complete the two factor process, e.g. to click on the verification link sent by email)
  • the PBX will create and store a permanent session for this login and return special session credentials for this new session
  • subsequent log-ins can be done using the session credentials instead of the user credentials

Please note that the PBX will create new session credentials for each such login. Also, the PBX will only store the last 8 such sessions. Excessive older sessions are deleted. For this reason, it is important that an application stores the session credentials received and uses them for all subsequent log-ins. Otherwise, some older sessions (which may be sessions created by the myApps client or other applications) would be removed.

Once the calling application is logged in to the PBX, the authentication towards the App Service can take place. The process is as follows:

  • the calling application requests a challenge from the App Service
  • the calling application passes this challenge to the PBX
  • the PBX (knowing the shared secret defined for the AppService) creates a hash from some information about the user
  • the calling application passes the hash to the App Service
  • the App Service computes the same hash and compares it to the one received from the application
  • if both match, the application is authenticated

WebSocket

App Services communicate using messages sent through WebSockets. The content of those messages has JSON syntax.

Although WebSockets are based on the HTTP protocol (for example, they usually use ports 80/443), they behave fundamentally different than HTTP connections in that they are asynchronous. With HTTP, all requests are synchronous, that is, each request is responded to by an answer. Also, only the HTTP client can send requests, not the server. With WebSocket however, both sides can (once they have agreed to upgrade the HTTP connection to a WebSocket connection) send messages at any time. Such messages may trigger 0, 1 or more response messages, depending on the application protocol. The protocol itself does not define a relation between a message and its response messages. As there is no such thing as a synchronous request/response cycle with WebSocket, an asynchronous programming model is required to take full advantage of the WebSocket approach.

JSON

JSON stands for JavaScriptObjectNotation. It is a subset of the syntax JavaScript uses to denote (complex) data constants. Here is an example: {"mystring":"a","myint":42}. As such, it allows to store the value of an object as a string and also to set the value of an object from a string (a process known as serialization/unserialization in many programming languages). JSON allows us to put the value of an object in to a WebSocket message and also of course to retrieve such value from a WebSocket message. Compared to XML (which more or less allows the same), it is much more compact.

Although JSON is related to JavaScript, most programming languages can deal with it. For example, PHP has the json_encode() and json_decode() functions, C# has the DataContractJsonSerializer class.

Here is a full example of the JSON message that might be used to initiate a log-in to the PBX

{
    "mt": "Login",
    "type": "session",
    "userAgent": "websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"
}

When working with App Services, there are two message members which are somehow special for all such services.

mt (string)
the message type. Each message must have an mt member which denotes the message type
src (string, optional)
this member may be sent along with messages sent to an App Service. If the message triggers responses, the App Service will copy the src member in to the responses. This allows the client to associate responses to the message that initiated them.

All other members (if any) are defined by the application protocol.

Definition of Application Protocols

Message types, their members and the message flow are part of the documentation in the software development kit (SDK) that comes with the App Platform. In this article, we only touch them for educational purposes.

PHP Sample Code

These scripts use an optional configuration from a file called my-pbx-data.php. If it is not present, default values are used.

To have the scripts proper data for your environment, create the file my-pbx-data.php as follows:

<?php
$pbxdns = "your-pbx-dns-or-ip";
$pbxuser = "your-pbx-user-name";
$pbxpw = "your-pbx-user-password";
$pbxapp = "your-app-object-name";

Access App services using user credentials

Our little example (available in websocket-sample.php) will connect to 2 App Services on the App Platform, Devices and Users to retrieve some configuration data. To authenticate towards the App service instances, an existing PBX user with appropriate rights is used.

PBX configuration

You will need a PBX which has been set up using the Install procedure.

Login

As discussed above, to do so, we need to log-in to the PBX and then connect and authenticate to the respective App Services.

// login to PBX and devices and users
$connector = new AppPlatform\AppServiceLogin(
        $pbxdns, new AppPlatform\AppUserCredentials($pbxuser, $pbxpw), array(
    $devicesspec = new AppPlatform\AppServiceSpec("innovaphone-devices"),
    $usersspec = new AppPlatform\AppServiceSpec("innovaphone-users"),
        ),
        true
);
$connector->connect();

To perform the necessary steps, an instance of class AppServiceLogin (note that in our sample code, all library code provided is in the AppPlatform name space) is used. The constructor arguments are

$PBX (string)
either the FQDN or the full websocket URI of the PBX . In the sample code, we use the FQDN sindelfingen.sample.dom
$credentials (AppUserCredentials)
the users Name and Password wrapped in an object of type AppUserCredentials
$appServiceSpec (AppServiceSpec[])
an array of App Service specifications to select the ones to connect to, each wrapped in an object of type AppServiceSpec

The call to connect() creates and authenticates the WebSocket connections to the App Services and the PBX. Note that a connection is made only if the user used for log-in has access to the selected App Service.

The code then verifies that the log-in to the PBX succeeded:

// look at the PBX login
if ($connector->getPbxA()->getIsLoggedIn()) {
    AppPlatform\Log::log("Logged in to PBX");
    $pbxws = $connector->getPbxWS();
    $pbxloginresult = ($connector->getPbxA()->getResults());
} else {
    AppPlatform\Log::log("Failed to log-in to PBX");
    exit;
}

If the login succeeded, the WebSocket connection to the PBX and the log-in messages are retrieved. Next step is to verify the respective connections to the App Services:

// look at devices
if ($connector->getAppAutomaton($devicesspec)->getIsLoggedIn()) {
    AppPlatform\Log::log("Logged in to Devices");
    $devicesws = $connector->getAppAutomaton($devicesspec)->getWs();
} else {
    AppPlatform\Log::log("Failed to log-in to Devices");
    exit;
}

// look at users
if ($connector->getAppAutomaton($usersspec)->getIsLoggedIn()) {

    AppPlatform\Log::log("Logged in to Users");
    $usersws = $connector->getAppAutomaton($usersspec)->getWs();
} else {
    AppPlatform\Log::log("Failed to log-in to Users");
    exit;
}

Eh voilà, we are connected to the 2 App Services we are interested in.

// now we have the authenticated websockets to our AppServices, so we can release the connector
$connector = null;

That was the easy part :-)

Implementing the WebSocket Protocol

Life was easy so far as we have used the AppServiceLogin utility class. Now we want to implement a conversation with our App Services using the WebSocket connections we have set up before.

Generally, you can of course use just any WebSocket library directly. We are using (and recommending) a derivative of the Textalk websocket classes. We simplified the code a bit and also added support for receiving WebSocket messages asynchronously. You will find this code in classes/textalk.class.php.

However, solely using the textalk classes leaves you with quite a bit of work to do. As discussed above, there are some challenges:

  • websocket is async by nature
you will need some support for receiving messages asynchronously at least. Aside from the support for asynchronous receipt of messages added to the Textalk classes, the sample code has some classes which allow you to easily create event driven code which processes messages whenever they come in and not when you expect them to come in
  • PBX login requires some fiddling with encryption schemes and netlogon protocol peculiarities

The sample code includes some classes to create finite state automatons. Also it has some classes derived from this, which actually implement the protocols required to log-in to the PBX and the App Services

Those extensions can be found in classes/websocket.class.php.

The Asynchronous Programming Model

PHP is not really nicely prepared for asynchronous programming. To deal with that, we have created a utility class called FinitStateAutomaton. This class handles the communication to a single App Service. This is why it has a WebSocket connection as argument to the constructor (our WebSocket implementation is actually called WSClient). The class will use this WebSocket to talk to the App Service. As we have seen above, the AppServiceLogin utility class creates such WSClient objects (which we retrieved by calling the getAppWebSocket() member function.

However, if you try to instantiate such a class (like $mya = new FinitStateAutomaton($mywebsocket) you will see that this is not possible, as the class is abstract. This means that there are some member functions missing which must be implemented by a derived class. These functions are those which know how to handle messages received from the App Service.

So here we go and define a derived class:

// an automaton which lists all devices in Devices
class DeviceLister extends AppPlatform\FinitStateAutomaton {
...
}

Devices by the way is the App Service that manages all physical devices that belong to a single innovaphone PBX installation. Our sample task now is to talk to Devices to find out which devices exist.

When we instantiate this derived class, we will provide the WSClient towards the Devices App Service.

$dl = new DeviceLister($devicesws);

In the base class, there is only one function declared as abstract:

    // this function at least must be overriden by any derived class
    abstract public function ReceiveInitialStart(\AppPlatform\Message $msg);

This function will be called by the base class, when a message with message type (mt, see above) of Start is received in the automaton state Initial. When a message is received and there is no appropriate Receive function defined, the message is discarded. Otherwise the message is passed to the Receive function and the derived class can handle it.

The Start is a little special. In fact, the FinitStateAutomaton class will send this pseudo-message to the derived class to start the automaton defined by the derived class. This message has nothing but the mt member. The derived class will usually send the first message to its App Service in this Receive function.

    public function ReceiveInitialStart(\AppPlatform\Message $msg) {
        AppPlatform\Log::log("Requesting Device List");
        $this->sendMessage(new AppPlatform\Message("GetDevices"));
    }

It is the sendMessage member function of the base class that is used to send a message to the App Service. The message itself is an instance of a class of type Message. The first argument of its constructor is the mt member of the message. All further arguments are optional. If they appear, they must appear in pairs of member/value. In the code above, the mt is "GetDevices".

Devices will respond to this message with a message that looks like so:

{
    "mt": "GetDevicesResult",
    "devices": [{
            "id": 2,
            "hwId": "029033410109",
            "name": "AP - sample.dom",
            "domain": 1,
            "product": "AppPlatform ARM",
            "version": "60002 dvl",
            "type": "APP_PLATFORM",
            "pbxActive": false,
            "online": true
        }, {
            "id": 3,
            "hwId": "0090333000af",
            "name": "ckl's IP232",
            "domain": 1,
            "product": "IP232",
            "version": "13r1 dvl [13.1102/125119/501]",
            "type": "PHONE",
            "pbxActive": false,
            "online": true
        }, {
            "id": 1,
            "hwId": "009033410109",
            "name": "PBX - sindelfingen.sample.dom",
            "domain": 1,
            "product": "IP811",
            "version": "13r1 dvl [13.1133/131094/200]",
            "type": "GW",
            "pbxActive": true,
            "online": true
        }]
}

As we see, the message type mt is "GetDevicesResult" so the ReceiveInitialGetDevicesResult member function of our derived class will be called:

    public function ReceiveInitialGetDevicesResult(\AppPlatform\Message $msg) {
        AppPlatform\Log::log("got " . count($msg->devices) . " Device Info(s)");
        $this->devices = array_merge($this->devices, $msg->devices);
        if (isset($msg->last) && $msg->last) {
            AppPlatform\Log::log("Last chunk");
            return "Dead";
        }
    }

Here we save the data in some class-local storage ($this->devices). More interesting is what we do if the message contains a member last which is set to true. In this case, the Receive function returns the name of the new state the automaton is supposed to be in. In our case, it is "Dead". "Dead" is a magic state name (just as "Initial" is) in that it tells the system that your automaton has finished. You could as well change the state name to something else (say, "NewState"). If further GetDevicesResult messages would appear, a member function ReceiveNewStateGetDevicesResult() would be called.

So here is the complete derived class:

// an automaton which lists all devices in Devices
class DeviceLister extends AppPlatform\FinitStateAutomaton {

    protected $devices = array();

    public function getDevices() {
        return $this->devices;
    }

    public function ReceiveInitialStart(\AppPlatform\Message $msg) {
        AppPlatform\Log::log("Requesting Device List");
        $this->sendMessage(new AppPlatform\Message("GetDevices"));
    }

    public function ReceiveInitialGetDevicesResult(\AppPlatform\Message $msg) {
        AppPlatform\Log::log("got " . count($msg->devices) . " Device Info(s)");
        $this->devices = array_merge($this->devices, $msg->devices);
        if (isset($msg->last) && $msg->last) {
            AppPlatform\Log::log("Last chunk");
            return "Dead";
        }
    }

}

Its as simple as that!

Have a look at the second derived class in the sample code (websocket-sample.php) called UserLister. It is a bit more complicated (as it handles more message types) but you will see the same mechanisms.

Running the Automatons

So far, we have not talked about who actually runs the automatons we create in our derived classes. You could think that this is done by calling something like $myauto = new myAuto($ws); $myauto->run(). However, it is done a little different. The reason for this is that it comes in handy once in a while when you can run several automatons concurrently.

Think of the log-in scenario we discussed above. In this scenario, we must talk both to the PBX and to one or more App Services. As we learned above, a single automaton only talks to a single App Service (or actually a PBX, as a PBX can act just like an App Service. This is known as PBX App).

Therefore, automatons are run by another utility class called Transitioner. This is instantiated with a list of automatons (which are in turn instantiated with their respective WSClients). We then can start and run all those automatons concurrently:

$dl = new DeviceLister($devicesws);
$ul = new UserLister($usersws, $pbxloginresult->loginResultMsg);
$t = new AppPlatform\Transitioner($dl, $ul);
$t->run();

For convenience however, the FiniteStateAutomaton also has a (trivial) member function run indeed. It will run only the automaton itself and comes in handy if you want to run a single automaton only.

    /**
     * utility function for the simple case you want to run a single (i.e. this) automaton only
     */
    public function run() {
        $auto = new Transitioner($this);
        $auto->run();
    }
Communicating between Automatons

When automatons run concurrently, you most likely will need to be able to exchange message between them. This can be done by the FinitStateAutomaton classes postEvent member function.

    /**
     * post an Event to other automatons, this function is synchronous (i.e. Receive*() member functions will be called directly)
     * @param Message $msg
     */
    protected function postEvent(Message $msg, $dst = null);

The function allows you to send a message to the other automatons (when $dst is null) or to a specific automaton. For an example, see the UserPBXLoginWithAppAutomaton and AppLoginViaPBXAutomaton which work in tandem using the postEvent mechanism.

Please note that messages sent by this function are handled synchronously by the target automatons (i.e the target automatons have already processed the posted event when postEvent returns).

Access App services using the service's secret

This sample code (in file pbxapi.php) accesses an App service using the secret as defined in an App object. No user account is required therefore. As a sample, the PbxAdminApi provided by the PBX itself is used.

This sample is new from build 1006.

PBX configuration

To access an API provided by the PBX, an App object is required. In this sample, we assume that

  • an App type object called pbxadminapi (this is the Name property) is created
  • it has ip411 as Password
  • in the object's App tab, the 'Admin' and 'PbxApi' check-marks are ticked in the Grant access to APIs section
Authentication

To authenticate towards an App service directly, the AppPlatform\AppLoginAutomaton (available in websocket.class.php) is used. As we intend to talk to an API provided by the PBX itself, we derive the class PbxAppLoginAutomaton. It only overrides the constructor to create the proper URL to connect to the PBX.

/*
 * authenticate towards the PBX as an App service
 */
class PbxAppLoginAutomaton extends AppPlatform\AppLoginAutomaton {

    /**
     * @var string PBX IP address
     */
    protected $pbxUrl;

    /**
     * @var \WebSocket\WSClient websocket to PBX
     */
    protected $pbxWS;

    function __construct($pbx, AppPlatform\AppServiceCredentials $cred, $useWS = false) {
        $this->pbxUrl = (strpos($pbx, "s://") !== false) ? $pbx :
                $this->pbxUrl = ($useWS ? "ws" : "wss") . "://$pbx/PBX0/APPS/websocket";
        // create websocket towards the well known PBX URI
        $this->pbxWS = new AppPlatform\WSClient("PBXWS", $this->pbxUrl);
        parent::__construct($this->pbxWS, $cred);
    }

}

Please note the relative URL path /PBX0/APPS/websocket used. This is required for direct API access. The path /PBX0/APPCLIENT/130000/websocket which is used in the AppServiceLogin (as shown in the previous sample code) can only be used for a user login. Apart from that, the provedure to access an API provided by an App service on an App platform is exactly the same.

To authenticate towards the PBX, we run the

$app = new PbxAppLoginAutomaton($pbxdns, new AppPlatform\AppServiceCredentials($pbxapp, $pbxpw));
$app->run();

As soon as this is completed, we are logged in to the PBX (we could verify the login success by calling $app->getIsLoggedIn().

Using the API

To use the API, we define a new automaton class (so it is derived from AppPlatform\FinitStateAutomaton):

// the class utilizes the PbxApi
class PbxApiSample extends AppPlatform\FinitStateAutomaton {

    public function ReceiveInitialStart(\AppPlatform\Message $msg) {
        $this->sendMessage(new AppPlatform\Message("GetStun", "api", "PbxAdminApi"));
    }

    public function ReceiveInitialGetStunResult(\AppPlatform\Message $msg) {
        return "Dead";
    }

}

This automaton will merely send a GetStun message to the API (note the additional api member that always needs to be set to the API identifier, PbxAdminApi in our case) and wait for the result. It terminates as soon as the result is received.

We instantiate the automaton and run it with the authenticated WebSocket we obtained earlier (retrieved from the PbxAppLoginAutomaton class using it's getWs() member function.

$pbxapi = new PbxApiSample($app->getWs(), "PBX");
$pbxapi->run();
Using the RCC API

If you want to use the RCC API instead, you can take a look at the sample rccapi.php.

To use this example, you need:

  • a PBX App Object named rccapi
  • the PBX App Object must have the password ip411 (just for using the example of course ...)
  • the PBX App Object doesn't need an App URL set, as it points to the local PBX by default
  • the PBX App Object must have the RCC flag set under App


In the PHP code you'll see the Initialize message which must be sent first. You'll receive UserInfo messages for all users, which have the rccapi App flag set in their Apps.

public function ReceiveInitialStart(\AppPlatform\Message $msg) {
    $this->sendMessage(new AppPlatform\Message("Initialize", "api", "RCC"));
}

public function ReceiveInitialUserInfo(\AppPlatform\Message $msg) {
    $this->log("UserInfo");
}
   
public function ReceiveInitialInitializeResult(\AppPlatform\Message $msg) {
    $this->log("InitializeResult");
    return "Dead";
}

This sample code is available from build 1012.

Access a PBX App service using a PBX user login

You might think: if the websocket authentication procedure used towards an API provided by the PBX is identical to the procedure for an API provided by an App service running on the Apps platform, why can't I use the approach shown in the first sample (websocket-sample.php) for the PBX too?

In fact, you can! The code would look much like the first example in websocket-sample.php, except you need to use a different service specification.

This sample is new from build 1007.

PBX configuration

To make this work, you must create the same App object (pbxadminapi) as before in pbxapi.php. However, in addition to that you must fill in its URL property so that it points to the PBX's API service access point: http://your-pbx-dns/PBX0/APPS/websocket.

Authentication

To login, you would use the AppPlatform\AppServiceLogin class again:

// login to PBX and devices and users
$connector = new AppPlatform\AppServiceLogin(
        $pbxdns, new AppPlatform\AppUserCredentials($pbxuser, $pbxpw), array(
    $apispec = new AppPlatform\AppServiceSpec("websocket", "APPS", "PBX0"),
        ),
        true
);
$connector->connect();

Note the new AppServiceSpec though!

The code will authenticate towards the PBX and towards the API websocket now (just like it did towards the PBX and the App platform services in the websocket-sample.php sample).

Using the API

The API can then be used just like before:

// the class utilizes the PbxApi
class PbxApiSample extends AppPlatform\FinitStateAutomaton {

    public function ReceiveInitialStart(\AppPlatform\Message $msg) {
        $this->sendMessage(new AppPlatform\Message("GetStun", "api", "PbxAdminApi"));
    }

    public function ReceiveInitialGetStunResult(\AppPlatform\Message $msg) {
        return "Dead";
    }

}

// AppPlatform\Log::setLogLevel("", "debug", true);
$pbxapi = new PbxApiSample($apiws, "PBX");
$pbxapi->run();

Access the App manager

(thanks to Forum user james99 who provided this solution)

3rd party input
this is 3rd party content not provided by innovaphone, see history for authors.
$connector = new AppPlatform\AppServiceLogin($pbxdns, new AppPlatform\AppUserCredentials($pbxuser, $pbxpw),
        array($managerspec = new AppPlatform\AppServiceSpec("manager")
        ), true);
$connector->connect();

// manager socket connection
if ($connector->getAppAutomaton($managerspec)->getIsLoggedIn()) {
    AppPlatform\Log::log("Logged in to app manager");
    $managerws = $connector->getAppAutomaton($managerspec)->getWs();
} else {
    AppPlatform\Log::log("Failed to log in to app manager");
    exit;
}

System Requirements

To run the sample code, you need

  • a platform that is able to run v13r1 PBX firmware (e.g. an IP411, but any other will do too)
  • a platform that can run the v13r1 app platform (the same IP411 would do), so if you choose to use a gateway, you will need an SSD
  • a web server running PHP 5.4 or up (the code has not been tested with PHP 7, but should run with no problems)
  • your favourite PHP IDE

You can download the PBX firmware from store.innovaphone.com.

Installation

The PBX and App Platform is installed using the Install.

PBX / App Platform

  • if you choose to use a gateway platform, install an SSD
  • upgrade your box (or IPVA) to the latest v13r1
  • you will need two extra IP address. One for the App Platform, one for the PBX
  • perform a factory reset
  • access the box and you will see the installer
  • before you can run the installer, you will need do create a DNS name for your PBX and App Platform
    • the easiest way to do this, is to use your box itself as an (additional) DNS server
    • access your PBX using the URL https://your-pbx-ip-address/admin.xml?xsl=admin.xsl to bypass the installer for a moment
    • go to Services/DNS
    • tick Enable DNS Server
    • add a New Resource Record of type A
    • use sindelfingen.sample.dom as Name
    • use your-pbx-ip-address as IP Address
    • add another New Resource Record of type A
    • use apps.sample.dom as Name
    • use your-app-platform-ip-address as IP Address
  • restart the box and access the installer again
  • complete the installer using DNS names set above, so that it installs a PBX and a fresh App Platform
  • be sure to note the Admin Password shown in the installer
  • the installer will ask for the name of an admin account. Be sure to note name and password. To make sure the sample code will run right away, use ckl as name here and pwd as password
  • for convenience, consider to not turn on two factor authentication

PHP Script

  • unpack the sample sources in to your web server's content directories
  • make sure PHP scripts can be executed in the .../sample/sources directory
  • open websocket-sample.php

You should now see the following output:

07/09/18 12:21:33.5742 0.000 0000.000 smsg AppPlatform\Transitioner: sent->PBXWS {"mt":"Login","type":"user","userAgent":"websocket.class.php (PHP WebSocket CKL-CELSIUS-W10)"}
07/09/18 12:21:33.5750 0.000 0000.000 smsg AppPlatform\Transitioner: received<-PBXWS {"mt":"Authenticate","type":"user","method":"digest","domain":"sample.dom","challenge":"3e6bf6a1ba658959"}
...

Known Problems

Missing php_openssl extension

In case following error appears, make sure to enable php_openssl in your php environment configuration:

Fatal error: Uncaught Error: Call to undefined function AppPlatform\openssl_random_pseudo_bytes()

Download

The sample code can be downloaded here