Using URSYS Messages - dsriseah/ursys GitHub Wiki
The URSYS library includes the net
addon, which implements a message subscriber/notifier system that allows web clients to talk to send data within an app instance or between each other. App instances just have to decide on the name of the message, and choose whether it's "local" to the instance or available to other apps on the network.
Tip
You can have as many handlers in your app instance defined across multiple modules. If more than one message handler exists, all handlers receive the message; in the case of UR.call()
, instead of a single data packet you'll get an array of them. The call is guaranteed to return only after all handlers have responded (note: as a development prototype tool, not all error cases are handled should a network transport error occur). This works for both local and network message handlers.
import * as UR from '@ursys/core';
UR.addMessageHandler('LOCAL_MESSAGE', data => {
console.log('got data',data);
// optionally return data
return { result: 'success' };
});
import * as UR from '@ursys/core';
UR.call('LOCAL_MESSAGE', { foo: 'bar' })
.then(data=>console.log('response',data);
alternate
(async()=>{
const data = await UR.call('LOCAL_MESSAGE, { foo: 'bar' });
console.log('response',data);
})()
In the local invocation scenario, URSYS messaging is like a custom event system that happens to look like a remote procedure call. There are four styles of URSYS message invocation:
-
call(mesg_name, data_object).then()
implements a local procedure call so you call other code modules without including them as a direct dependency. It returns a Promise. -
send(mesg_name, data_object)
is for one-way sends in the current app. It returns a Promise also, which resolvestrue
when a message is successfully routed (but not executed). Used for making updates when you don't need confirmation that the data has been processed. -
signal(mesg_name, data_object)
is for one-way broadcasts. It returns immediately whether the message is successfully received or not. Used for raising status changes as a kind of custom event. -
ping(mesg_name)
is for testing for the existence of a message handler, returning a count.
The above methods are for local invocation on the same endpoint. The syntax is extended to work across the network so you can call other webapp instances, with the server invisibly brokering the transaction for you.
URSYS messages are comprised of a channel and a message. If the channel is not specified, it's assumed to be local. There are two network channels currently defined: NET:
and SRV:
. If you leave out the channel, it's assumed to be a local invocation. Here's an example:
-
MY_MESSAGE
is a locally-defined handler -
NET:MY_MESSAGE
is a network-wide handler -
SRV:MY_MESSAGE
is a network-wide handler that is defined only on net brokers, providing services.
Similar to the local declaration, except with the addition of the NET:
prefix and use of the UR.registerRemoteMessages()
to send the message list to the upstream URNET message dispatch server (can also be invoked when the messages change through addition registerMessage()
and deregisterMessage()
calls.
import * as UR from '@ursys/core';
UR.addMessageHandler('NET:NETWORK_MESSAGE', data => {
console.log('got data',data);
// optionally return data
return { result: 'success' };
});
UR.registerRemoteMessages();
Note
Local messages are distinct from network messages. In other words, MY_MESSAGE
is not the same as NET:MY_MESSAGE
and have different handlers. You can not substitute netCall
for call
to "convert" a message from local to network.
Likewise, invocations to the network-defined message handlers look the same, except with the addition of the NET:
prefix and using the method netCall
instead of call
.
import * as UR from '@ursys/core';
UR.netCall('NET:NETWORK_MESSAGE', { foo: 'bar' })
.then(data=>console.log('response',data);
alternate
(async()=>{
const data = await UR.netCall('NET:NETWORK_MESSAGE, { foo: 'bar' });
console.log('response',data);
})()
The available methods for network messages mirror the local names:
-
netCall(mesg_name, data_object).then()
returns a Promise, and is used for implementing services like remote procedure calls between webapps. Multiple webapp instances of different types that are on the same URNET can trade data with each other. It's guaranteed to return only when data from the remote handlers return their data payloads. -
netSend(mesg_name, data_object)
is for one-way sends. It returns a Promise also, which resolves when a message is successfully routed by the dispatching server. This is often used to deploy dictionary updates from a module. In other bad cases, it's used to invoke an action whennetCall
would be more suitable. It does not route to the originating invoker (check if this is true) -
netSignal(mesg_name, data_object)
is for one-way broadcasts. These are used for signaling operational state changes that affect multiple apps, including the local It returns immediately whether the message is successfully routed or not. Used for raising status changes that are of interest to multiple handlers across different local subsystems. UnlikenetSend
, the signal is also received by the local app instance if it implements a particular message. -
netPing(mesg_name)
is for counting apps that implement a message, returning an array of URSYS addresses. Can be used to poll whether a particular application has come online on the URNET.
URNET is a "hub and spoke" message architecture. The hub is the "URNET Server" which accepts connections from "URSYS Clients". Since the server has access to a file system and other fancy stuff, it is often also used to home system service messages.
System Service Handlers use the message prefix SRV:
, which can only be registered on the URNET server implementing the underlying network connectivity.
import * as UR from '@ursys/core';
import * as DB from 'database';
UR.addMessageHandler('SRV:DB_GET_ALL', async () => {
const { records } = await DB.getAllCollections();
return { records };
});
UR.addMessageHandler('SRV:DB_SAVE', async (data) => {
const { collection, records } = data;
const result = await DB.updateCollection(collection, records);
const { err, changeSet } = result;
return { err, changeSet };
});
URNET server services use the same interface as clients, and can also act as clients on the network.
For details on how the URNET server and client classes work, see URSYS Network Concepts.