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.

Local Messages

Defining a Local Message Handler

pseudocode

import * as UR from '@ursys/core';

UR.addMessageHandler('LOCAL_MESSAGE', data => {
  console.log('got data',data);
  // optionally return data
  return { result: 'success' };
});

Local Invocation of a Message Handler

pseudocode

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);
})()

Typical Uses of Local Messages

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 resolves true 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.

Network Messages

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.

Network Message Handlers

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.

pseudocode

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.

Network Message Invocation

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.

pseudocode

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 when netCall 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. Unlike netSend, 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.

Service Messages

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.

pseudocode

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.

Additional Information

For details on how the URNET server and client classes work, see URSYS Network Concepts.

⚠️ **GitHub.com Fallback** ⚠️