-
Notifications
You must be signed in to change notification settings - Fork 0
3. Usage
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.
- Init event. Slave gateway sends message to master frame to notify the master Gateway that it’s ready for load
- Load event. Master gateway sends the message to the slave that it has the all necessary data, and it can proceed with the loading
- Loaded event. Slave sends the event to the master frame notifying it that it’s loaded and ready for user interaction.
- Message exchange. More in chapter 3.3
Gateway is initialized by calling the module and passing the proper configuration object.
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');
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
andplugins
properties. Src accepts relative path to worker script, or instance of web worker. All events which arrive to gateway exceptResize
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
Gateway exposes emit()
method for messages exchange between frames.
(NOTE) Alias: emit
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)
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 ‘*’,
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
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)
-
postMessage
does 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.
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
});
})
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.Add
, Betslip.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
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.
Unsubscribe will remove subscribed event and all registered callbacks.
(NOTE) Alias: off
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
Clearing subscriptions will remove all subscribed events and callbacks.
Gateway.clearSubscriptions()
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: {...}
})
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)
This event is sent from master when iframe is back again in focus.
Gateway.emit(frameId, {
action : $slaveId,
data: {}
}, origin)
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.
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
}
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.
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()]
...
});
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()]
...
});
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.
Slave gateway accepts worker object in initial configuration. Worker property can object with configuration:
worker: {
src : string | Worker instance,
plugins : array
}
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]
}
})
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'
}
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.
This concept enables infinite nesting levels.