Skip to content

3. Usage

Lovro Vidović edited this page May 15, 2024 · 20 revisions

3.1. Initialization Phases

To initialize Master<=>Slave communication, component runs through 2 phases automatically in background. We can think of these phases as a handshake process where necessary data is exchanged.

initialization phase

  1. Init event. Slave gateway sends message to master frame to notify the master Gateway that it’s ready for load
  2. Load event. Master gateway sends the message to the slave that it has the all necessary data, and it can proceed with the loading
  3. Loaded event. Slave sends the event to the master frame notifying it that it’s loaded and ready for user interaction.
  4. Message exchange. More in chapter 3.3

3.2. Configuration

Gateway is initialized by calling the module and passing the proper configuration object.

3.2.1. Master gateway configuration

var Gateway = require(‘master’);
Gateway({
    slaves/products : {
         ${slaveId/productId}: {
                frameId : frameId,
                data : {},
                init : function,
                loaded : function
         }
    },
    allowedOrigins : [],
    debug : bool,
);

Configuration object contains properties:

Name Description Type Required
slaves/products Object with product details Object N
allowedOrigins Array of allowed origins Array N
debug Debug setting bool N
  • slaves/products - Object which contains info about 3rd party product which will be integrated in master frame.
  • allowedOrigins - List of origins which are allowed to exchange messages.  It should contain base URL from src attribute of iframe. This property is array, so it can contain multiple URL’s. Default value is set to ‘*’ meaning no security will be implemented. Although this setting in not required, it’s highly recommended,
  • debug - Gateway debug setting. If the setting has positive value, all messages which Gateway receives will be logged out

Slaves obj:

Name Description Type Required Default
frameId ID of iframe where product is located string Y ~
data Data passed to slave to run the product load phase object N ~
init Product initialization callback function N ~
loaded Product loaded callback function N ~
autoResize Resize iframe on content change Boolean N true
  • init - Callback which is triggered in the first phase of initialization. Callback will be triggered when product is ready for load.
  • loaded - Callback which will be triggered on the last phase of load. This callback is triggered when the product is loaded. Usually it can be used for for UI gimmicks like removing the loader

Slaves can be added after initialization on the fly using the method addSlave:

Gateway.addSlave({
  frameId : 'dummy',
  slaveId : 'dummy'
});

removeSlave method will remove slave by passing the slaveId property:

Gateway.removeSlave('dummy');

3.2.2. Slave gateway configuration

var Gateway = require(‘slave’);
Gateway({
    slaveId/productId : string,
    data : object,
    load: function,
    allowedOrigins : array,
    debug : bool,
    worker : string || Worker instance
    eventPropagation : object
    eventListeners : object,
    calculateFixedAndAbsoluteElements: boolean
});
Name Description Type Required
slaveId/productId Unique identifier of the product string Y
data Data which will be passed to master on init phase Object N
load Load phase callback(2nd pahse) function N
allowedOrigins Array of allowed origins array N
debug Debug setting bool N
worker Web worker configuration object N
eventPropagation Events which will be propagated to master frame Object N
eventListeners Events which are required from master frame Object N
calculateFixedAndAbsoluteElements Calculate fixed and absolute elements in height boolean N
plugins List of plugins to enable on initialization array N
  • slaveId/productId - Unique slave id. It is required prop in configuration. Based on slaveId, master gateway will handle message exchange.
  • data - Object which will be passed in init phase(1st phase) to master. It doesn’t have any strict interface. Usually contains product configuration
  • load - Callback triggered in second phase of product initialization. It can receive argument from master frame
  • worker – Optional web worker configuration. It receives object with src and plugins properties. Src accepts relative path to worker script, or instance of web worker. All events which arrive to gateway except Resize will be sent to worker. Worker chapter will be discussed later.
  • eventPropagation – List of events which will be propagated to the master frame
  • eventListeners – List of events which are required from master frame. e.g. requiring scroll event from master frame to handle lazy load…
  • calculateFixedAndAbsoluteElements - By default fixed and absolute elements are outside normal page flow so having the fixed element or absolute as first child in body should increase body size. By default this element heights will be excluded from calculations, but it is possible turn them on

3.3. Message exchange

Gateway exposes emit() method for messages exchange between frames.

(NOTE) Alias: emit

3.3.1. Master to slave

Gateway.emit(frameId, {
    action : 'Betslip.Add',
    slaveId/productId : $slaveId
}, origin)

Object must contain name of action and $slaveId property. $slaveId property is the unique id of the product to which message is dispatched. Origin param is the origin of sender. That origin must be enabled in slave gateway in order to process the message. If origin is omitted origin will be set to ‘*’.

Master exposes sendToAll method which will send data to all registered slaves:

Gateway.sendToAll(data, origin)

3.3.2. Slave to master

Gateway.emit({
    action : 'Betslip.Add',
    data: {...}
}, origin);

Object must contain name of action. Origin param is the origin of sender. That origin must be enabled in platform gateway in order to process the message. If origin is omitted origin will be set to ‘*’,

3.3.3.Async message exchange

It is possible to send master <-> slave messages with use of promises.

Master -> Slave

// master
{gatewayInstance}.emitAsync(frameId, {
    action : 'Get.Data',
}, origin, rejectTimeout).then(function(res) {
  // should write {someData: 'data'}
   console.log(res)
})
.catch(function(err){
 // console.log(err)
});

// slave
{gatewayInstance}.on('Get.Data', function(event) {
  // do some logic...
  setTimeout(function(){
    event.resolve({someData: 'data'});
  // or event.reject({someData: 'data'});
 }, 1000);
});

rejectTimeout is optional but it can be used for fallback if our promise is never resolved. origin is also optional.

Slave -> Master

// slave
{gatewayInstance}.emitAsync({
    action : 'Betslip.Add',
}).then(function(res) {
  // should write 'bet added'
   console.log(res)
})
.catch(function(err){
 // console.log(err)
});

// master
{gatewayInstance}.on('Betslip.Add', function(event) {
  // do some logic...
  setTimeout(function(){
    event.resolve('bet added');
  // or event.reject('failed to add bet');
 }, 1000);
});

(NOTE) Alias: request or emitAsync

3.3.4. Exposing the functions between frames

Executing functions between frames is by default impossible due the multiple reasons:

  • Different frames are run in different JS contexts so calling cross frame functions is not possible (unless sites are on same origin)
  • postMessagedoes not support passing functions between frames (unless converted to string)

Gateway enables passing of the functions which then could be executed between frames. This isn't provided by some magic, Gateway actually persists the original function in internal state in callers context and creates the hash which is then used between frames to call the function in original window. Functions are exposed between frames by declaring the callback or callbacks property inside emit or emitAsync payload.

Example:

{gatewayInstance}.emit({
    action : 'betslip.add',
    callback: {
        method: function()
    }
}, origin)

{gatewayInstance}.emit({
    action : 'betslip.add',
    callbacks: [
        {
            method: function(),
            ...
        }
    ]
}, origin)
Name Description Type Required
callback Single exposed function object N
callbacks Array of objects with exposed functions array N

callback object or object declared in callbacks array requires function named as method. When Gateway receives this payload, internal hash is created and function declared in method is exposed for cross frame execution.

3.3.5. Executing the functions between frames

Exposed functions are now available for execution from different context. When Gateway receives message which has defined callback or callbacks property, it wraps those declarations inside function which will do the all auto magic of cross context execution. Actually Gateway wraps passed callbacks in 2 methods:

  • method which will call the exposed callback without any ack if that function is actually called (fire and forget method)
  • methodAsync which returns promise when method is called

Example:

Gateway.subscribe('Betslip.Add', function(data) {
    data.callback.method();
})

or 

Gateway.subscribe('Betslip.Add', function(data) {
    data.callback.methodAsync().then(function() {
        //Success
    });
})

3.3.6. Message subscription

emit will send messages between frames, but we need to subscribe to messages which we want to process. It is standard pub/sub pattern. Subscribtion format:

Gateway.subscribe('Betslip.Add', callback)

(NOTE) Action names are case sensitive, meaning that ‘betslip.Add’ and ‘Betslip.Add’ actions are different.

When the event Betslip.Add occurs, registered callback will be triggered.

It is possible to use namespaced subscriptions. e.g. If one of the products subscribes to Betslip.* event with callback, events Betslip.AddBetslip.Remove will trigger the Betslip.* callback. It is possible to subscribe using * wildcard, meaning that every event will trigger registered callback.

(NOTE) Alias: on

Service provides isSubscribed method which allows us to check if there is existing subscription(s) for event. Return value is boolean.

(IMPORTANT) Messages prefixed with Master and Slave are system reserved messages and therefore they will be ignored

3.3.7.One time subscription

It is possible to subscribe once by calling once method:

{gatewayInstance}.once('betslip.add',function)

After event betslip.add occurs, subscription will be automatically disposed.

3.3.8. Message unsubscribe

Unsubscribe will remove subscribed event and all registered callbacks.

(NOTE) Alias: off

3.3.9. Single listener unsubscribe

Single listener can be unsubscribed if the subscription is referenced in variable by calling remove method. e.g.

var betslipAdd = Gateway.subscribe('Betslip.Add', callback);

betslipAdd.remove(); //This will remove only this listener

3.3.10. Clear subscription

Clearing subscriptions will remove all subscribed events and callbacks.

Gateway.clearSubscriptions()

3.3.11. Event muting

In order to stop receiving events from slave, slave frame can be muted. Mute will ignore all messages sent from Master, and it will prevent event propagation from Slave => Master.

Mute can be called via event Slave.Mute:

Gateway.emit(frameId, {
    action : 'Slave.Mute',
    timeout: ${number},
    slaveId: $slaveId
}, origin)

timeout is optional and it can be used in situations when we want to mute slave for X seconds.

Mute state can be removed by sending the event:

Gateway.emit(frameId, {
    action : 'Slave.Unmute',
    slaveId : $slaveId
}, origin)

However, the are cases when you want to send event even if a slave frame is in the mute state. You can force event to be sent by setting enforceEvent to true.

Gateway.emit(frameId, {
    action : 'Player.BalanceUpdated',
    slaveId/productId : $slaveId
    enforceEvent: true
}, origin)

Same can be done from the Slave point of view:

// slave
Gateway.emit({
    action: 'Peripherals.Print',
    enforceEvent: true,
    data: {...}
})

3.3.12. Event Awake and Snooze

2.3.12.1 Slave.Snooze

This event is sent from master when iframe is sent to background. It is signal so slave can take needed actions.

Snooze can be called via event Slave.Snooze:

Gateway.emit(frameId, {
    action : $slaveId,
    data: {}
}, origin)

2.3.12.2 Slave.Awake

This event is sent from master when iframe is back again in focus.

Gateway.emit(frameId, {
    action : $slaveId,
    data: {}
}, origin)

3.4. Event exchange (keyboard, mouse)

GG supports event exchange between frames. For now only supported events are keyup, keydown, keypress, click, scroll, touchstart and touchend. Each event has specific interface.

3.4.1. Event propagation

Slave gateway can declare in initial configuration which events it will dispatch to the master frame.

eventPropagation : {
  {$eventType} : array || object || bool // array and object only for keyboard events
}

For keyboard events we can declare array of keys or combination of keys which will be dispatched to master frame. Array of bindings can be written as list of:

  • keyCodes(85)
  • keys('u')
  • combined [‘u’, 73].

Combinations must be provided with + sign - Ctrl+85 It is advised to use the keyCodes for key binding definition for sake of normalization across browsers.

Keyboard events also allow use of wildcard * which will propagate all events from one frame to another. Wildcard * can be used like in next example:

{
  keyup : '*'
}

This will propagate all keyup events. Wildcard * can be used in combination with blacklist array of keycodes which marks keycodes which won't be propagated even when wildcard is used for propagation:

{
  keyup : {
    types: '*',
    blacklist: [63, 66]
  }
}

In case when wildcard is used with blacklist option {$eventType} config must be in object format with required field types and optional blacklist

Slave exposes method addEventsForPropagation which can be used to add new events which will be propagated from Slave to Master. Argument format:

{
  {$eventType} : array || bool // array only for keyboard events
}

Beside using the slave API for adding the new events, they can be added automatically from Master frame by sending the event Slave.AddEvents with data scheme:

{
  {$eventType} : array || bool // array only for keyboard events
}

3.4.2. Event listeners

Slave gateway can define which events it wants to receive from master frame. Event listener configuration is the same as for the event propagation config.

eventListeners: {
  {$eventType} : array || bool // array only for keyboard events
}

Just like eventPropagation configuration only keyup, keydown, keypress, click, scroll, touchstart and touchend events are supported.

3.5. Plugins

GG comes with plugins that can be turned on/off depending on context of an application.

Example of telling Gateway to instantiate a plugin

import Gateway from '@nsoft/seven-gravity-gateway/slave';
import DummayPlugin from '@nsoft/seven-gravity-gateway/dummay-plugin';

Gateway({
    ...
    plugins: [new DummayPlugin()]
    ...
});

3.5.1 Barcode scanner

This plugin pick up's scanned barcodes via a hand held scanner, when document focus is in iframe, and it emits final result to Master.

Example of instantiating barcode plugin

import Gateway from '@nsoft/seven-gravity-gateway/slave';
import BarcodeScanPlugin from '@nsoft/seven-gravity-gateway/plugin-barcode-scan';

Gateway({
    ...
    plugins: [new BarcodeScanPlugin()]
    ...
});

3.5.2 Retail Plugin

If you want to load and use all available GG plugins for retail, you can simply add this plugin to the Gateway configuration.

Example of instantiating retail plugin

import Gateway from '@nsoft/seven-gravity-gateway/slave';
import RetailPlugin from '@nsoft/seven-gravity-gateway/plugin-retail';

Gateway({
    ...
    plugins: [new RetailPlugin()]
    ...
});

Note: You will not need to include both, Retail and BarcodeScan plugins, since BarcodeScan plugin will be bundled up into Retail plugin.

3.6. Using Slave gateway with worker

Slave gateway accepts worker object in initial configuration. Worker property can object with configuration:

worker: {
  src : string | Worker instance,
  plugins : array
}

3.6.1. Plugins

src accepts string or Worker instance. Installed web worker will receive all messages except Resize event. Configuration accepts optional plugins property which accepts array of supported plugins.

Gateway comes with predefined plugins which can be used only in combination with worker. In order to initialize the worker => plugin communication plugin reference needs to be passed inside worker plugin array. Storage plugin can be included from dist file as simple script. Example:

<script src="../../node_modules/seven-gravity-gateway/dist/slave.js"></script>
var storagePlugin = require('seven-gravity-gateway/plugin-storage');

Gateway({
  ...
  worker : {
    src : path / instance
    plugins : [storagePlugin]
  }
})

3.6.2. Storage Plugin

Currently only one plugin is supported and it is storage plugin. Storage plugin serves for storage manipulation from worker because localStorage and sessionStorage are undefined in Web Worker context. Plugin allows manipulation with localStorage and sessionStorage

Storage plugin has same methods as Storage Web API in addition with isSuppored method. :

  • key
  • getItem
  • setItem
  • removeItem
  • clear
  • isSuppored

Storage API is consumed with Plugin.Storage.$methodName - method name is capitalized

Example of usage:

//Worker context
self.postMessage({
  action: 'Plugin.Storage.GetItem',
  keyName: 'dummy',
  driver :'localStorage',
});

Worker will recieve message from plugin with data

{
  action: 'Plugin.Storage.GetItem',
  keyName: 'dummy',
  keyValue: '$value,
  driver: 'localStorage'
}

3.7. Nesting frames

Same frame can load at the same time Master and Slave module, meaning that at the same time it can be slave to parent frame, but it can have it’s own slave frame.

nesting frames

This concept enables infinite nesting levels.