BLO - Backend Logic

Description

Accessing the rapidM2M BACKEND API made easy

OVERVIEW

BLO_Abstract:
BLO → Backend LOgic
Programming language is Javascript and Node.js is used as runtime.
  • ECMAScript modules are forced for development (nodejs.org/api/esm.html.)
    import instead of require!
  • Node version depends on the myDatanet appliance!
  • Helperlibrary: ~bapi

  • Site assignment and lifecycle

    For each APM (Application Model) and development phase only one BLO exists!


    Backwardscompatibility

  • a BLO should always be backwards compatible to all previous versions
  • Logging

  • use the ~bapi's log interface for applicative log messages
  • use console.log for development purposes only. It will be disabled from phase stage onwards.
  • log messages will be stored for 7 days
  • BLO_Modes:
    There are two modes for the BLO. Restricted (default mode) and global/unlimited.

    Restricted

    The BLO only has only access to it's according sites.
  • Accoding sites:
    • all sites from the same APM (Application Model) and development phase
    • the customer relation of this site doesn't matter
  • global customer/user/sites queries aren't allowed
  • Global

    Full access level to all resources
  • during installation of the app the user will be prompted with an install prompt.
  • there is still the restriction to the development phase
  • BAPI

    Description

    Accessing the rapidM2M BACKEND API made easy

    OVERVIEW

    BAPI_Abstract:
    The BAPI provides a single wrapper module to access all interfaces used for BACKEND programming.
  • KEY/VALUE STORAGE API
  • LOCAL API METHODS (HTTP REST)
  • LOCAL EVENT DRIVEN API
  • LOCAL API MANAGEMENT
  • AUXILIARY FUNCTIONS (e.g. stamp treatment, ...)
  • Monitoring of notification triggers sent by the MACHINE and forwarding of eMail/SMS messages to certain users
  • Cross-MACHINE (loop-) control and monitoring
  • Any data- or time-triggered central business intelligence logic
  • The BAPI is available for services running locally at the BACKEND, only.

    BASIC

    apm_id

    the BLO's Application Model Id

    Example:
    //get the blo's apm id const apm_id = BAPI.apm_id;
    blo_id

    the BLO's unique id

    Example:
    //get the blo's apm id const blo_id = BAPI.blo_id;
    blo_secret

    the BLO's authentication string to access the api

    apm_phase

    the current development phase

    dev - development phase
    stage - staging phase
    release - release phase
    blo_level

    the blo's access level

    restricted - restricted access to a subset of the api paths
    global - full access to all api paths
    timeout

    specifies the number of milliseconds before the request time is over.

    If the request takes longer than timeout, the request will be aborted.
    The default timeout is 15000 milliseconds (15 seconds).
    Example:
    //set the request timeout to 30 seconds BAPI.timeout = 30000;

    COMMON

    error

    contains additional information about the error

    params

    optional object with query parameters, or null/undefined

    Date object are automatically converted to rapidM2M stamps
    for .del() requests most of the time you can set it to null
    reply

    valid json data provided by reply body

    apipath

    api path with optional references '$xxx' to .placeholders{}

    STORAGE API

    bapistorageintro:
    The Storage interface allows, for example, the addition, modification, or deletion of stored data items.
    Stores data with no expiration date, and gets cleard only through JavaScript, or removing the application via the app center.
    You can either use BAPI.storage.xxx or storage.xxx directly anywhere in you code.
    storage.getItem(keyName )

    When passed a key name, will return that key's value.

    keyName : String - A String containing the name of the key you want to retrieve the value of.
    returns - A String containing the value of the key. If the key does not exist, null is returned.
    Example:
    let value = BAPI.storage.getItem(keyName);
    storage.setItem(keyName, keyValue)

    When passed a key name and value, will add that key to the storage, or update that key's value if it already exists.

    keyName : String - A String containing containing the name of the key you want to create/update.
    keyValue : String - A String containing the value you want to give the key you are creating/updating.
    Example:
    BAPI.storage.setItem('bgcolor', 'red'); BAPI.storage.setItem('font', 'Helvetica'); BAPI.storage.setItem('image', 'myCat.png');
    storage.removeItem(keyName)

    When passed a key name, will remove that key from the storage.

    keyName : String - A String containing the name of the key you want to remove.
    Example:
    BAPI.storage.setItem('bgcolor', 'red'); BAPI.storage.setItem('font', 'Helvetica'); BAPI.storage.setItem('image', 'myCat.png'); BAPI.storage.removeItem('image');
    storage.length

    Returns an integer representing the number of data items stored in the Storage object.

    Example:
    BAPI.storage.setItem('bgcolor', 'yellow'); BAPI.storage.setItem('font', 'Helvetica'); BAPI.storage.setItem('image', 'cats.png'); BAPI.storage.length; // Should return 3
    storage.clear()

    When invoked, will empty all keys out of the storage.

    Example:
    BAPI.storage.setItem('bgcolor', 'yellow'); BAPI.storage.setItem('font', 'Helvetica'); BAPI.storage.setItem('image', 'cats.png'); BAPI.storage.clear();

    LOGGING

    log.info(message[,...args])

    Info message

    message : String - A printf-like format string
    You can either use BAPI.log.xxx or log.xxx directly anywhere in you code.
    Example:
    BAPI.log.info("..."); BAPI.log.syserr("..."); BAPI.log.error("..."); BAPI.log.warning("..."); BAPI.log.useraction("..."); BAPI.log.sysaction("..."); BAPI.log.debug("..."); //or use the shorthand log.info("..."); log.syserr("..."); //...
    log.syserr(message)
    message : → log.info()
    log.error(message)
    message : → log.info()
    log.warning(message)
    message : → log.info()
    log.useraction(message)
    message : → log.info()
    log.sysaction(message)
    message : → log.info()
    log.debug(message)
    message : → log.info()
    Debug messages are only available during development.
    For staging or release BLO's you can temporary enable them from the BLO Monitor

    LOCAL API METHODS (HTTP REST)

    get(path, params)

    HTTP REST GET request

    path : apipath
    params : params
    returns : reply
    throws : error
    Example:
    try { //Query the logged-in user’s profile and customer list. const userProfile = await BAPI.get("1/me"); } catch(error) { //if the request fails, the error object contains further information why. } //usage with placeholders try { BAPI.placeholders.$cid="cust1"; const customer = await BAPI.get("/1/customers/$cid"); } catch(error) { //if the request fails, the error object contains further information why. }
    put(path, params)

    HTTP REST PUT request

    path : apipath
    params : params
    returns : reply
    reply contains only data if the api path returns any
    throws : error
    Example:
    //this example shows how to modify a site's profile //define what should be modified: const profile = { note: "This site was modified by the BAPI.put() request" } try { await BAPI.put("1/customers/mycustomer/sites/mysite", profile); } catch(error) { //if the request fails, the error object contains further information why. } //usage with placeholders try { BAPI.placeholders.$cid="cust1"; BAPI.placeholders.$sid="site1"; const customer = await BAPI.get("/1/customers/$cid/sites/$sid", profile); } catch(error) { //if the request fails, the error object contains further information why. }
    post(path, params)

    HTTP REST POST request

    works like the .put() request
    del(path, params)

    HTTP REST POST request

    works like the .put() request
    sendFile(path, file, params)

    SEND a file to the backend

    Common use would be: add a file to a sites filelist (.../api/1/cusomters/<cid>/sites/<sid>/files)
    path : apipath
    file : {filepath|filestream} - path to local file or a filestream
    params : params
    returns : reply
    reply contains only data if the api path returns any
    throws : error
    Examples:
    //write a file to a site based on the local path: const fileMeta = await BAPI.sendFile('1/customers/<cid>/sites/<sid>/files', './myLocalFile.txt', { filename: 'myLocalFile.txt' }); //write a file to a site base on a fileStream const stream = fs.createReadStream('./myLocalFile.txt'); const fileMeta = await BAPI.sendFile('1/customers/<cid>/sites/<sid>/files', stream, { filename: 'myLocalFile.txt' }); //write a file to a site based on a Buffer import { PassThrough } from 'stream'; const myFileBuf = Buffer.alloc(10); //Creates a zero-filled Buffer of length 10. const fileStream = new PassThrough(); fileStream.end(myFileBuf); fileStream.path = './myLocalFile.txt'; //The path attribute is neccessary! const fileMeta = await BAPI.sendFile('1/customers/<cid>/sites/<sid>/files', fileStream, { filename: 'myLocalFile.txt' });

    LOCAL API EVENT DRIVEN (WEBSOCKET)

    bapiwsintro:
    Access event driven API resources. The events are queued to grant their timely order.
    Each resource may be subscribed just once!
    subscribe(resource, opts)

    add new watcher for the given resource

    resource : String
    sites - site updates according to the apm and it's development phase
    config0-C - config updates according to the apm and it's development phase
    histdata0-9 - histdata updates according to the apm and it's development phase
    files - file updates according to the apm's sites and their development phase
    alarms - alarm updates according to the apm's sites and their development phase
    customers - global customer updates
    users - global user updates
    devices - global device updates
    opts : Object - optional options
    select:Array - array of fieldnames; used with 'histdata' resources
    ignore_phase:Boolean - get events across all development phases (dev, staging, release)
    site related resources only; default: false
    returns : subscription
    onUpdate
    onDelete
    onInitialized
    unsubscribe
    Example:
    Have a look at the subscribeExample
    onUpdate(data, context)

    triggers when data is updated/created

    data : Object - new data (full data-set of subscribed resource)
    NOT changes only
    context : Object - context information according to the data object
    _uid - ID of the data record
    Extra references provided in the context object as described in the table below
    resource_uidextra references
    sitessitecustomer_uid
    config0-Csitecustomer_uid
    histdata0-9sitecustomer_uid
    filesfilesite_uid
    customer_uid
    alarmsalarmsite_uid
    customer_uid
    customerscustomerNONE
    usersuserNONE
    devicesdevicecustomer_uid
    onDelete(_uid)

    triggers when data is deleted

    _uid - id of the deleted record
    see the onUpdate context table to get a clue about the _uid's reference
    onInitialized()

    triggers when the general inquiry finished

    Request all matching data up on connect to grant initialize of some local cache.
    If you don't call this function you will get data change notifications only (create, delete, update)
    This function is called only once
    unsubscribe()

    remove resource watcher

    subscribeExample:
    Simple example how to generate a local site cache
    const cache = {}; const subscription = BAPI.subscribe('sites') .onUpdate(async (data, context) => { const {_uid, customer_uid} = context; //the according sites uid and customer relation //add/update the site's information in the cache cache[_uid] = data; }) .onDelete(_uid => { //remove the deleted site from the local cache delete cache[_uid]; }) .onInitialized(() => { //we're just calling this function to initially get all sites log.info("SITE CACHE INITIALIZED"); }); //If you don't need the event updates anymore, unsubscribe from the resource subscription.unsubscribe();

    LOCAL API MGMT

    extend(userlevel, method, path, onRequest, options)

    add new resource to "api/ext...//"

    If you add an api route, the route starts with a different path based on the current development phase.
  • dev → "api/extdev/<apm_id>/..."
  • stage → "api/extstage/<apm_id>..."
  • release → "api/ext/<apm_id>..."
  • userlevel : Number - Minimum user-level to access path
    Values:
    -1 = public - any user, even if not logged in
    1...6 - user must be logged in
    7 = global - administrative user
    method : String - "GET", "PUT", "POST" or "DELETE"
    path : String - URL relative to the current development phase's ext. (/api/ext... ) with optional
    declaration (not consumption!) of placeholders:
  • Predefined
      • $cid - stands for customer_id
      • $sid - stands for site_id
      • $did - stands for device_id
      • $uid - stands for user_id
  • any app-specific $xxx
  • predefined placeholders undergo automatic access control mechanisms.
  • start the path with your app-id to keep it unique e.g. "myapp/res1…"
  • example:
    'myapp/$cid/abc/$sid/xyz' resolves an access to:
    'www.mybackend.com/ext/myapp/cust1/abc/site1/xyz'
    where "cust1" and "site1" are interpreted as placeholder values
  • onRequest : Function - Process the request
    options : Object - Additional options like custom headers
    headers:Array - Specify the headers you want to receive in the blo
    onRequest(placeholders, indata, references)
    placeholders - names as given with placeholders
    the path variable
    eg.: { "site_id": ..., "customer_id": ..., "headers": ..., ...}
    indata : Object - as sent by the client
    references : Object - contains reference information about the current request
    user - additional information about the requesting user
    Structure of the object is the same as the user object provided by the api
    If the BLO mode is "restricted" this property will be null
    headers - if custom headers are passed in the options object they are accessible within this object.
    {headers : {custom_header1 : value, ... }}
    return - various return values possible
    object:
    statusCode - Http status code
    data - Object with data to be returned to the client (null = no data)
    statusCode only:
    statusCode - Http status code
    data object only:
    Object - eg.: {mydata: 'Hello World'}
    statusCode defaults to the HTTP request default success value
    No return value:
    statusCode defaults to the HTTP request default success value
    Example:
    BAPI.extend(1, 'PUT', 'sample/extend', async (placeholders, indata) => { //some data processing .... //return either full object to control the statusCode: return { statusCode: 400, data: {err: 'Wrong data was sent'} }; //return just the statusCode if you don't have any additional response data: return 400; //return the response data object only return { processedValue: 1 } //or just return nothing - a successful statusCode will be returned });
    extendExample:
    This is a simple example how to add application specific API resource
    //Collect via BAPI.subscribe('sites') all relevant information for each site into a local cache const cache = {}; // return minified overall information about a site BAPI.extend( 2, 'GET', 'abstract/$sid', (placeholders,indata) => { const cached_site= cache[ placeholders.site_id]; if (!cached_site) { return 500; // reject access uncached site with internal error } else { return { statusCode: 200, data: { name : cached_site.site.name, gsmlvl: cached_site.site.device ? cached_site.site.device.gsm.level : null, status: cached_site.config0.appstatus } } } });

    TIMESTAMP TREATING

    dateToStamp(

    Convert any javascript date object to a rapidm2m timestamp.

    Trailing zeros getting removed from the string!
    return : stamp:String - formatted as "YYYYMMDDHHmmssSSS"
    Example:
    //converts the current date into the rapidm2m timestamp equivalent const stamp = BAPI.dateToStamp(new Date());
    toStamp(dt)

    Convert times or date objects to rapidm2m timestamp

    dt : 0/null/undefined, (ISO-)string, js Date object, number ms since 1970-01-01 0:00 UTC
    0/null/undefined returns the current time
    return : stamp:String - as "YYYYMMDDHHmmssSSS"
    Example:
    /* * Date given as javascript date * returns "20200617113145" - trailing zeros getting removed! */ const stamp = BAPI.toStamp(new Date('2020-06-17 11:31:45')); /* * Date given as milliseconds since 1970-01-01 0:00 UTC * returns "2020020220202" - trailing zeros getting removed! */ const stamp = BAPI.toStamp(1580671220000); /* * Date given as ISO String * returns "20200617113145213" - trailing zeros getting removed! */ const stamp = BAPI.toStamp("2020-06-17T11:31:45.213Z");
    stampToDate(stamp)

    Convert rapidm2m timestamp to date object

    stamp : String - as "YYYYMMDDHHmmssSSS"
    return : Object - Javascript Date object
    Example:
    /* * Date given as rapidm2m timestamp * returns the date object for the given stamp */ const date = BAPI.stampToDate("20200617113145213");
    stampToMS(stamp)

    Convert rapidm2m timestamp to unix timestamp

    stamp : String - as "YYYYMMDDHHmmssSSS"
    return : Number - Milliseconds since 1970-01-01 0:00 UTC
    Example:
    /* * Date given as rapidm2m timestamp * returns the milliseconds since 1970-01-01 0:00 UTC */ const milliseconds = BAPI.stampToMS("20200617113145213");
    stampFormat(stamp)

    Convert rapidm2m timestamp to a date string

    stamp : String - as "YYYYMMDDHHmmssSSS"
    return : String - as "YYYY-MM-DD HH:mm:ss.SSS"
    Example:
    /* * Date given as rapidm2m timestamp * returns the equivalent date string. ("2020-06-17 11:31:45") */ const datestring = BAPI.stampFormat("20200617113145");
    stampAddMS(stamp, ms)

    Add or remove milliseconds to/from stamp

    stamp : String - as "YYYYMMDDHHmmssSSS"
    ms : Number - any number in milliseconds
    return : String - as "YYYYMMDDHHmmssSSS"
    Example:
    /* * Date given as rapidm2m timestamp * returns the new rapidm2m timestamp with the added or removed amount of milliseconds; ("20200202202022") */ const newStamp = BAPI.stampAddMS("2020020220202", 2000);

    EXPERT

    subscribePath(path, opts)

    Add new watcher for api resource

    path : String - string with optional references '$xxx'
    1/sites - global list of sites
    1/system/db/uto/:tag - global list of the specific tag
    1/alarms - global alarms
    ...
    opts : Object - → same as subscribe
    returns
    onUpdate
    context information references to the subscribed path
    onDelete
    onInitialized
    unsubscribe
    extendAPI(userlevel, method, path, onRequest, options)

    add new resource to "api/ext.../"

    Use with care! Your API route won't be unique anymore!
    see .extend how to use this function
    createClient(opts)

    Creates a new instance of the BAPI class

    opts
    If you want to use the library with an other host you can do it like it is shown in the following example.
    const opts = { host: 'https://another.rapidm2m.com/api/', username: 'MyUsername', password: 'MyPassword', /* If you need to use another authorization you can use the following auth property auth: '', */ timeout: 15000, debug: false, x3rdparty: false //optional - only if you want to access some 3rd party service } const FAPI = BAPI.createClient(opts); /* * use FAPI like the BAPI * await FAPI.get( ...) * await FAPI.put( ...) * await FAPI.post( ...) * await FAPI.del( ...) */