Feature-rich lightweight WAMP (Web Application Messaging Protocol) Javascript implementation
See the code
Amazingly fast, feature-rich, lightweight and zero dependency (by default) WAMP (Web Application Messaging Protocol) client for browser and node.js, written in TypeScript
Wampy.js is a TypeScript library that runs both in browser and node.js environments, and even in react native
environment. It implements WAMP v2 specification on top of WebSocket object, also provides additional
features like auto-reconnecting. It has no external dependencies (by default) and is easy to use. The package
ships with full TypeScript type declarations (.d.ts) out of the box. Also, it provides (starting from v7.1)
command line wamp client which can be extremely helpful in quick check/debug existing WAMP-based APIs.
Wampy.js supports the following WAMP roles and features:
Wampy supports the following serializers:
In node.js environment Wampy is compatible with the following websocket clients:
For convenience, all API documentation is also available as a GitBook here.
import { Wampy } from 'wampy';
const wampy = new Wampy('/ws/', { realm: 'AppRealm' });
try {
await wampy.connect();
} catch (e) {
console.log('connection failed', e);
}
try {
await wampy.subscribe('system.monitor.update', (eventData) => {
console.log('Received event:', eventData);
});
} catch (e) {
console.log('subscription failed', e);
}
try {
const res = await wampy.call('get.server.time');
console.log('RPC called. Server time:', res.argsDict.serverTime);
} catch (e) {
console.log('RPC call failed', e);
}
// Somewhere else for example
await wampy.publish('system.monitor.update');
// or just ignore promise if you don't need it
wampy.publish('client.message', 'Hi guys!');
Wampy.js can be installed using npm:
npm install -S wampy
For browser usage download the latest browser.zip archive and add wampy-all.min.js file to your page. It contains all auth plugins and serializers. They are available as global objects:
window.JsonSerializer = JsonSerializer;
window.MsgpackSerializer = MsgpackSerializer;
window.CborSerializer = CborSerializer;
// WampyCra is not available in the browser bundle due to its dependency on node:crypto
window.WampyCryptosign = wampyCryptosign;
<script src="wampy-all.min.js"></script>
If you don't plan to use other serializers then JSON or any auth plugins, just include wampy.min.js.
<script src="wampy.min.js"></script>
Wampy.js exports the following components that you can import as needed. All exports include TypeScript
type declarations (.d.ts):
{
"exports": {
".": { // Main Wampy class
"types": "./dist/esm/wampy.d.ts",
"import": "./dist/esm/wampy.js",
"require": "./dist/cjs/wampy.cjs"
},
"./JsonSerializer.js": {
"types": "./dist/esm/serializers/json-serializer.d.ts",
"import": "./dist/esm/serializers/json-serializer.js",
"require": "./dist/cjs/serializers/json-serializer.cjs"
},
"./CborSerializer.js": {
"types": "./dist/esm/serializers/cbor-serializer.d.ts",
"import": "./dist/esm/serializers/cbor-serializer.js",
"require": "./dist/cjs/serializers/cbor-serializer.cjs"
},
"./MsgpackSerializer.js": {
"types": "./dist/esm/serializers/msgpack-serializer.d.ts",
"import": "./dist/esm/serializers/msgpack-serializer.js",
"require": "./dist/cjs/serializers/msgpack-serializer.cjs"
},
"./cryptosign.js": { // Cryptosign authentication plugin
"types": "./dist/esm/auth/cryptosign/wampy-cryptosign.d.ts",
"import": "./dist/esm/auth/cryptosign/wampy-cryptosign.js",
"require": "./dist/cjs/auth/cryptosign/wampy-cryptosign.cjs"
},
"./wampcra.js": { // WAMP-CRA authentication plugin
"types": "./dist/esm/auth/wampcra/wampy-cra.d.ts",
"import": "./dist/esm/auth/wampcra/wampy-cra.js",
"require": "./dist/cjs/auth/wampcra/wampy-cra.mjs"
}
}
}
Wampy cli tool exposes almost the same API options to the command line interface. You can use all types of
authorization, publish, subscribe, register and call any URI. Some WAMP Actions provides additional helper options
(e.g. mirror option in register command that allows to return back the invocation payload to the caller).
Cli tool is charged with rich help descriptions, examples and even shell auto-completion script. All parameters may be passed as cmd args or via related ENV Vars for convenience. So you can for example export WAMP Router URI and realm to the environment and provide only wamp action parameters via cmd.
You can install wampy cli tool globally or call it by using npx:
npm install -g wampy
# After that you can invoke wampy
wampy -h
# or just run wampy with npx
npx wampy -h
Check the wampy -h or wampy --help for the available commands and global options and check the help for a specific
command by issuing wampy <call|register|publish|subscribe> -h.
To make use of shell auto-completion features just add output of wampy completion to your shell config:
wampy completion >> ~/.zshrc
# or
wampy completion >> ~/.bashrc
The completion command is hidden from the wampy -h output to not pollute the main use flow as it is only needed
once.
Please refer to Migrating.md for instructions on upgrading major versions.
Below is a description of the exposed public API.
Wampy ships with built-in TypeScript type declarations — no separate @types/wampy package is needed.
Wampy constructor can take 2 parameters:
realm. For node.js environment it's also necessary to specify ws - websocket module. See description below.// in browser
wampy = new Wampy();
wampy = new Wampy('/my-socket-path');
wampy = new Wampy('wss://socket.server.com:5000/ws', { autoReconnect: false });
wampy = new Wampy({ reconnectInterval: 1*1000 });
// in node.js
import { w3cwebsocket as w3cws } from 'websocket';
wampy = new Wampy(null, { ws: w3cws });
wampy = new Wampy('/my-socket-path', { ws: w3cws });
wampy = new Wampy('wss://socket.server.com:5000/ws', { autoReconnect: false, ws: w3cws });
wampy = new Wampy({ reconnectInterval: 1*1000, ws: w3cws });
// or using ws example
import WebSocket from 'ws';
wampy = new Wampy(null, { ws: WebSocket });
wampy = new Wampy('/my-socket-path', { ws: WebSocket });
wampy = new Wampy('wss://socket.server.com:5000/ws', { autoReconnect: false, ws: WebSocket });
wampy = new Wampy({ reconnectInterval: 1*1000, ws: WebSocket });
Json serializer will be used by default. If you want to use msgpack or cbor serializer, pass it through options. Also, you can use your own serializer if it is supported on the WAMP router side.
// in browser
wampy = new Wampy('wss://socket.server.com:5000/ws', {
serializer: new MsgpackSerializer()
});
wampy = new Wampy({
serializer: new CborSerializer()
});
// in node.js
import { Wampy } from 'wampy';
import { MsgpackSerializer } from 'wampy/MsgpackSerializer';
import { CborSerializer } from 'wampy/CborSerializer';
import WebSocket from 'ws';
wampy = new Wampy('wss://socket.server.com:5000/ws', {
ws: WebSocket,
serializer: new MsgpackSerializer()
});
wampy = new Wampy({
ws: w3cws,
serializer: new CborSerializer()
});
Returns Wampy configuration options. See setOptions() down below for the full list of available options.
wampy.getOptions();
Receives a newOptions object as a parameter, where each property is a new option to be set and returns a Wampy instance.
Options attributes description:
false. Enable debug logging.null. User-provided logging function. If debug=true and no logger specified, console.log will be used.true. Enable auto reconnecting. In case of connection failure, Wampy will try to reconnect to WAMP server, and if you were subscribed to any topics, or had registered some procedures, Wampy will resubscribe to that topics and reregister procedures.value: 2000 (ms). Reconnection Interval in ms.25. Max reconnection attempts. After reaching this value .disconnect()
will be called. Set to 0 to disable limit.null. WAMP Realm to join on server. See WAMP spec for additional info.null. Custom attributes to send to router on hello.strict. Can be changed to loose for less strict URI validation.null. Authentication (user) id to use in challenge.[]. Array of strings of supported authentication methods.{}. Additional authentication options for Cryptosign-based authentication.
See Cryptosign-based Authentication section and WAMP Spec CS for more info.{}. Authentication helpers for processing different authmethods flows.
It's a hash-map, where key is an authentication method and value is a function, that takes the necessary user
secrets/keys and returns a function which accepts authmethod and challenge info and returns signed challenge answer.
You can provide your own signing functions or use existing helpers. Functions may be asynchronous.import * as wampyCra from 'wampy/wampcra';
import * as wampyCS from 'wampy/cryptosign';
wampy.setOptions({
authPlugins: {
// No need to process challenge data in ticket flow, as it is empty
ticket: ((userPassword) => (() => userPassword ))(),
wampcra: wampyCra.sign(secret),
cryptosign: wampyCS.sign(privateKey)
},
authMode: 'auto'
});
manual. Possible values: manual|auto. Mode of authorization flow. If it is set
to manual - you also need to provide onChallenge callback, which will process authorization challenge. Or you
can set it to auto and provide authPlugins (described above). In this case the necessary authorization flow
will be chosen automatically. This allows to support few authorization methods simultaneously.null. Callback function.
It is fired when wamp server requests authentication during session establishment.
This function receives two arguments: auth method and challenge details.
Function should return computed signature, based on challenge details.
See Challenge Response Authentication section, WAMP Spec CRA,
Cryptosign-based Authentication section and WAMP Spec CS for more info.
This function receives welcome details as an argument.null. Callback function. Fired on closing connection to wamp server.null. Callback function. Fired on error in websocket communication or if error happens
during auto reconnection flow (as it can not be bound to explicit API calls).null. Callback function. Fired every time on reconnection attempt.null. Callback function. Fired every time when reconnection succeeded.
This function receives welcome details as an argument.null. User provided WebSocket class. Useful in node environment.null. User provided additional HTTP headers (for use in Node.js environment)null. User provided WS Client Config Options (for use in Node.js environment).
See docs for WebSocketClient, tls.connect options.JsonSerializer. User provided serializer class. Useful if you plan to use other encoders
instead of default json.{ json: jsonSerializer }. User provided hashmap of serializer instances for
using in Payload Passthru Mode. Allows to specify a few serializers and use them on per message/call basis.wampy.setOptions({
reconnectInterval: 1000,
maxRetries: 999,
onClose: () => { console.log('See you next time!'); },
onError: () => { console.log('Breakdown happened'); },
onReconnect: () => { console.log('Reconnecting...'); },
onReconnectSuccess: (welcomeDetails) => { console.log('Reconnection succeeded. Details:', welcomeDetails); }
});
Returns the status of last operation. This method returns an object with attributes:
code is integer, and value > 0 means error.error is Error instance of last operation. Check errors types exposed by wampy.reqId is a Request ID of last successful operation. It is useful in some cases (call canceling for example).const defer = wampy.publish('system.monitor.update');
console.log(wampy.getOpStatus());
// may return
// { code: 1, error: UriError instance }
// or { code: 2, error: NoBrokerError instance }
// or { code: 0, error: null }
Returns the WAMP Session ID.
wampy.getSessionId();
Connects to wamp server. url parameter is the same as specified in Constructor.
Returns a Promise that's either:
try {
await wampy.connect();
} catch (e) {
console.log('connection failed', e);
}
await wampy.connect('/my-socket-path');
const defer = wampy.connect('wss://socket.server.com:5000/ws');
Disconnects from wamp server. Clears all queues, subscription, calls. Returns a Promise that's either:
await wampy.disconnect();
Aborts WAMP session and closes a websocket connection.
If it is called on handshake stage - it sends the abort message to wamp server (as described in spec).
Also clears all queues, subscription, calls. Returns wampy instance back.
wampy.abort();
With Ticket-based authentication, the client needs to present the server an authentication ticket -
some magic value to authenticate itself to the server. It could be a user password, an authentication token or
any other kind of client secret. To use it you need to provide "ticket" in "authmethods", "authid" and
the "onChallenge" callback as wampy instance options.
'use strict';
// Ticket authentication
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'joe',
authmethods: ['ticket'],
onChallenge: (method, info) => {
console.log('Requested challenge with ', method, info);
return 'joe secret key or password';
}
});
// Promise-based ticket authentication
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'micky',
authmethods: ['ticket'],
onChallenge: (method, info) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Requested challenge with ', method, info);
resolve('micky secret key or password');
}, 2000);
});
}
});
Wampy.js supports challenge response authentication. To use it you need to provide the "authid" and the "onChallenge"
callback as wampy instance options. Also, Wampy.js supports wampcra authentication method with a little helper
plugin "wampy/wampcra". Just import wampy/wampcra and use provided methods as shown below.
'use strict';
import { Wampy } from 'wampy';
import * as wampyCra from 'wampy/wampcra'; // or import exact functions
import { w3cwebsocket as w3cws } from 'websocket';
// Manual authentication using signed message
wampy = new Wampy('wss://wamp.router.url', {
ws: w3cws, // just for example in node.js env
realm: 'realm1',
authid: 'joe',
authmethods: ['wampcra'],
onChallenge: (method, info) => {
console.log('Requested challenge with ', method, info);
return wampyCra.signManual('joe secret key or password', info.challenge);
}
});
// Promise-based manual authentication using signed message
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'micky',
authmethods: ['wampcra'],
onChallenge: (method, info) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Requested challenge with ', method, info);
resolve(wampyCra.signManual('micky secret key or password', info.challenge));
}, 2000);
});
}
});
// Manual authentication using salted key and pbkdf2 scheme
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'peter',
authmethods: ['wampcra'],
onChallenge: (method, info) => {
const iterations = 100;
const keylen = 16;
const salt = 'password salt for user peter';
console.log('Requested challenge with ', method, info);
return wampyCra.signManual(wampyCra.deriveKey('peter secret key or password', salt, iterations, keylen), info.challenge);
}
});
// Automatic CRA authentication
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'patrik',
authmethods: ['wampcra'],
onChallenge: wampyCra.sign('patrik secret key or password')
});
Wampy.js supports cryptosign-based authentication. To use it you need to provide authid, onChallenge callback
and authextra as wampy instance options. Also, Wampy.js supports cryptosign authentication method
with a little helper plugin "wampy/cryptosign". Just import wampy/cryptosign and use provided methods
as shown below.
The authextra option may contain the following properties for WAMP-Cryptosign:
| Field | Type | Required | Description |
|---|---|---|---|
| pubkey | string | yes | The client public key (32 bytes) as a Hex encoded string, e.g. 545efb0a2192db8d43f118e9bf9aee081466e1ef36c708b96ee6f62dddad9122 |
| channel_binding* | string | no | If TLS channel binding is in use, the TLS channel binding type, e.g. "tls-unique". |
| challenge | string | no | A client chosen, random challenge (32 bytes) as a Hex encoded string, to be signed by the router. |
| trustroot | string | no | When the client includes a client certificate, the Ethereum address of the trustroot of the certificate chain to be used, e.g. 0x72b3486d38E9f49215b487CeAaDF27D6acf22115, which can be a Standalone Trustroot or an On-chain Trustroot |
*: channel_binding is not supported yet. And may be supported only in node.js environment.
'use strict';
import { Wampy } from 'wampy';
import * as wampyCS from 'wampy/cryptosign';
// or you can import only the "sign" method
// import { sign } from 'wampy/cryptosign';
// Manual authentication using signed message
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'joe',
authmethods: ['cryptosign'],
authextra: {
pubkey: '545efb0a2192db8d43f118e9bf9aee081466e1ef36c708b96ee6f62dddad9122'
},
onChallenge: (method, info) => {
console.log('Requested challenge with ', method, info);
return wampyCS.sign('joe secret (private) key')(method, info);
}
});
// Promise-based manual authentication using signed message
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'micky',
authmethods: ['cryptosign'],
authextra: {
pubkey: '545efb0a2192db8d43f118e9bf9aee081466e1ef36c708b96ee6f62dddad9122'
},
onChallenge: (method, info) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Requested challenge with ', method, info);
resolve(wampyCS.sign('micky secret (private) key')(method, info));
}, 2000);
});
}
});
// Automatic CryptoSign authentication
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'patrik',
authmethods: ['cryptosign'],
authextra: {
pubkey: '545efb0a2192db8d43f118e9bf9aee081466e1ef36c708b96ee6f62dddad9122'
},
onChallenge: wampyCS.sign('patrik secret (private) key')
});
If you server provides multiple options for authorization, you can configure wampy.js to automatically choose
required authorization flow based on authmethod requested by server.
For this flow you need to configure the following options:
authid. Authentication id to use in challengeauthmethods. Supported authentication methodsauthextra. Additional authentication optionsauthPlugins. Authentication helpers for processing different authmethods challenge flowsauthMode. Mode of authorization flow. Should be set to autoonChallenge. onChallenge callback. Is not used when authMode=autoimport { Wampy } from 'wampy';
import { sign as CraSign } from 'wampy/wampcra';
import { sign as CryptoSign } from 'wampy/cryptosign';
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'patrik',
authmethods: ['ticket', 'wampcra', 'cryptosign'],
authextra: { // User public key for Cryptosign-based Authentication
pubkey: '545efb0a2192db8d43f118e9bf9aee081466e1ef36c708b96ee6f62dddad9122'
},
authPlugins: {
ticket: ((userPassword) => (() => userPassword ))(),
wampcra: CraSign(userSecret),
cryptosign: CryptoSign(userPrivateKey)
},
authMode: 'auto',
onChallenge: null
});
Subscribes for topicURI events.
Input Parameters:
Returns a Promise that's either:
await wampy.subscribe('chat.message.received', (eventData) => { console.log('Received new chat message!', eventData); });
try {
const response = await wampy.subscribe('some.another.topic',
(eventData) => {
console.log('Received topic event', eventData);
}
);
console.log('Successfully subscribed to topic: ' + response.topic);
} catch (e) {
console.log('Subscription error:' + e.error);
}
Unsubscribe subscription from receiving events.
Parameters:
Returns a Promise that's either:
const f1 = (data) => { console.log('this was event handler for topic') };
await wampy.unsubscribe('subscribed.topic', f1);
const defer = wampy.unsubscribe('chat.message.received');
Publish a new event to topic.
Parameters:
Returns a Promise that's either:
await wampy.publish('user.logged.in');
await wampy.publish('chat.message.received', 'user message'); // will be sent as ['user message1']
await wampy.publish('chat.message.received', ['user message1', 'user message2']);
await wampy.publish('user.modified', { field1: 'field1', field2: true, field3: 123 });
await wampy.publish('chat.message.received', ['Private message'], { eligible: 123456789 });
try {
await wampy.publish('user.modified', { field1: 'field1', field2: true, field3: 123 });
console.log('User successfully modified');
} catch (e) {
console.log('User modification failed', e.error, e.details);
}
Make an RPC call to topicURI.
Parameters:
Returns a Promise that's either:
Important note on progressive call results:
For getting a progressive call results you need to specify progress_callback in advancedOptions.
This callback will be fired on every intermediate result. But the last one result or error
will be processed on promise returned from the .call(). That means that final call result
will be received by call promise resolve handler.
const result = await wampy.call('server.time');
console.log('Server time is ' + result.argsList[0]);
try {
await wampy.call('start.migration');
console.log('RPC successfully called');
} catch (e) {
console.log('RPC call failed!', e.error);
}
try {
await wampy.call('restore.backup', { backupFile: 'backup.zip' });
console.log('Backup successfully restored');
} catch (e) {
console.log('Restore failed!', e.error, e.details);
}
Make an RPC progressive invocation call to topicURI.
Input parameters to this function are the same as described in the call() above. The difference is in the result.
Returns an object containing the result promise and the sendData function
which is supposed to be used to send additional data chunks in bounds of the
initiated remote procedure call:
([payload], [advancedOptions]):
let pc;
try {
pc = ws.progressiveCall('sum.numbers', 1);
} catch (e) {
console.log('RPC call failed!', e.error);
}
// Somewhere where you have additional data to be send to the procedure
try {
pc.sendData(2);
pc.sendData(3);
pc.sendData(4);
// This is final data chunk, so the Callee can understand that
// no more input data will be send and it can finish its
// calculations and send the result.
// Note that nothing stops the Callee to send the
// intermediate results if that makes sense.
pc.sendData(5, { progress: false });
} catch (e) {
console.log('RPC call send data failed!', e.error);
}
// And here we are waiting for the final call result
const res = await pc.result
console.log("Res:", res); // Res: 15
RPC invocation cancelling.
Parameters:
Returns a Boolean or throws an Error:
true if successfully sent canceling messageError if some error occurredconst defer = wampy.call('start.migration');
defer
.then((result) => console.log('RPC successfully called'))
.catch((e) => console.log('RPC call failed!', e));
status = wampy.getOpStatus();
wampy.cancel(status.reqId);
RPC registration for invocation.
Parameters:
Returns a Promise that's either:
Registered PRC during invocation will receive one hash-table argument with following attributes:
{ progress: true } for intermediate results.RPC can return no result (undefined), any single value, array or hash-table object:
"progress": true, which
indicates, that it's a progressive result, so there will be more results in the future.
Be sure to unset "progress" on last result message.const sqrt_f = function (data) { return { result: data.argsList[0]*data.argsList[0] } };
await wampy.register('sqrt.value', sqrt_f);
try {
await wampy.register('sqrt.value', sqrt_f);
console.log('RPC successfully registered');
} catch (e) {
console.log('RPC registration failed!', e);
}
Also, wampy supports rpc with asynchronous code, such as some user interactions or xhr, using promises. For using this functionality in old browsers you should use polyfills, like es6-promise. Check browser support at can i use site.
const getUserName = () => {
return new Promise((resolve, reject) => {
/* Ask user to input his username somehow,
and resolve promise with user input at the end */
resolve({ argsList: userInput });
});
};
wampy.register('get.user.name', getUserName);
Also, it is possible to abort rpc processing and throw error with custom application specific data. This data will be passed to caller onError callback.
Exception object with custom data may have the following attributes:
Note: Any other type of errors (like built in Javascript runtime TypeErrors, ReferenceErrors) and exceptions are caught by wampy and sent back to the client's side, not just this type of custom errors. In this case the details of the error can be lost.
const getSystemInfo = () => {
// Application logic
// for example, if you need to get data from db
// and at this time you can't connect to db
// you can throw exception with some details for client application
const UserException = () => {
this.error = 'app.error.no_database_connection';
this.details = {
errorCode: 'ECONNREFUSED',
errorMessage: 'Connection refused by a remote host.',
database: 'db',
host: '1.2.3.4',
port: 5432,
dbtype: 'postgres'
};
this.argsList = ['Not able to connect to the database.'];
this.argsDict = {};
};
throw new UserException();
};
await wampy.register('get.system.info', getSystemInfo);
try {
await wampy.call('get.system.info');
} catch (error) {
console.log('Error happened', error);
}
RPC unregistration from invocations.
Parameters:
Returns a Promise that's either:
await wampy.unregister('sqrt.value');
try {
wampy.unregister('sqrt.value');
console.log('RPC successfully unregistered');
} catch (e) {
console.log('RPC unregistration failed!', e);
}
During wampy instance lifetime there can be many cases when error happens: some
made by developer mistake, some are bound to WAMP protocol violation, some came
from other peers. Errors that can be caught by wampy instance itself are stored
in opStatus.error, while others are just thrown.
This allows, for example, convenient handling of different types of errors:
import {Wampy, Errors} from 'wampy';
const wampy = new Wampy('/ws/', { realm: 'AppRealm' });
try {
await wampy.call('start.migration');
console.log('RPC successfully called');
} catch (error) {
console.log('Error happened!');
if (error instanceof Errors.UriError) {
// statements to handle UriError exceptions
} else if (error instanceof Errors.InvalidParamError) {
// statements to handle InvalidParamError exceptions
} else if (error instanceof Errors.NoSerializerAvailableError) {
// statements to handle NoSerializerAvailableError exceptions
} else {
// statements to handle any unspecified exceptions
}
}
Wampy package exposes the following Error classes:
For errors attributes look at src/errors.ts file.
Wampy.js supports custom attributes in advancedOptions for protocol extensibility as defined in WAMP specification section 3.1.
Any option matching the pattern _[a-z0-9_]{3,} (starting with underscore, followed by at least 3 alphanumeric characters or underscores) will be passed through as-is to the WAMP router. This allows for custom extensions and router-specific features.
Supported in:
Examples:
// Custom tracking and priority attributes
await wampy.call('api.process.data', { data: 'test' }, {
_tracking_id: 'req_12345',
_priority: 'high',
_custom_auth: 'bearer_token_xyz',
timeout: 5000
});
// Custom routing hints
await wampy.call('distributed.service', payload, {
_route_to_region: 'us-west',
_load_balancer_hint: 'sticky_session',
_retry_policy: 'exponential'
});
Note: Custom attributes are only passed for methods that support them. Standard WAMP options (like timeout, disclose_me, etc.) are handled separately and don't need the underscore prefix.
From v5.0 version there is option to provide custom serializer.
Custom serializer instance must meet a few requirements:
encode (data) method, that returns encoded datadecode (data) method, that returns decoded dataprotocol string property, that contains a protocol name. This name is concatenated with
"wamp.2." string and is then passed as websocket subprotocol http header.isBinary boolean property, that indicates, is this a binary protocol or not.Take a look at json-serializer.ts or msgpack-serializer.ts as examples.
For TypeScript users, the Serializer interface is available in src/serializers/serializer.ts
and can be imported from the package for implementing custom serializers.
Starting from v6.2.0 version you can pass additional HTTP Headers and TLS parameters to underlying socket connection
in node.js environment (thnx websocket library). See example below. For wsRequestOptions you can pass any option,
described in tls.connect options documentation.
import { Wampy } from 'wampy';
import { w3cwebsocket as w3cws } from 'websocket';
const wampy = new Wampy('wss://wamp.router.url:8888/wamp-router', {
ws: w3cws,
realm: 'realm1',
additionalHeaders: {
'X-ACL-custom-token': 'dkfjhsdkjfhdkjs',
'X-another-custom-header': 'header-value'
},
wsRequestOptions: {
ca: fs.readFileSync('ca-crt.pem'),
key: fs.readFileSync('client1-key.pem'),
cert: fs.readFileSync('client1-crt.pem'),
host: 'wamp.router.url',
port: 8888,
rejectUnauthorized: false, // this setting allow to connect to untrusted (or self signed) TLS certificate,
checkServerIdentity: (servername, cert) => {
// A callback function to be used (instead of the builtin tls.checkServerIdentity() function)
// when checking the server's hostname (or the provided servername when explicitly set)
// against the certificate. This should return an <Error> if verification fails.
// The method should return undefined if the servername and cert are verified.
if (servername !== 'MyTrustedServerName') {
return new Error('Bad server!');
}
}
}
});
Wampy.js uses mocha and chai for tests (all written in TypeScript) and c8 for code coverage. Wampy sources are mostly all covered with tests!
# Build the project first (required for CLI tests)
> npm run build
# Run TypeScript type checking
> npm run typecheck
# Run all tests (node + browser-wrappers + karma browser)
> npm test
# Or run specific test suites
> npm run test:node-no-browser-wrappers
> npm run test:node-no-crossbar
> npm run test:browser-wrappers
> npm run test:browser
# Lint the codebase
> npm run lint
# For code coverage report run
> npm run cover
# and then open coverage/lcov-report/index.html
Wampy.js library is licensed under the MIT License (MIT).
Copyright (c) 2014 Konstantin Burkalev
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Thanks JetBrains for support! Best IDEs for every language!
TypeScript
97.8%
JavaScript
2.2%
Feature-rich lightweight WAMP (Web Application Messaging Protocol) Javascript implementation
See the code
Amazingly fast, feature-rich, lightweight and zero dependency (by default) WAMP (Web Application Messaging Protocol) client for browser and node.js, written in TypeScript
Wampy.js is a TypeScript library that runs both in browser and node.js environments, and even in react native
environment. It implements WAMP v2 specification on top of WebSocket object, also provides additional
features like auto-reconnecting. It has no external dependencies (by default) and is easy to use. The package
ships with full TypeScript type declarations (.d.ts) out of the box. Also, it provides (starting from v7.1)
command line wamp client which can be extremely helpful in quick check/debug existing WAMP-based APIs.
Wampy.js supports the following WAMP roles and features:
Wampy supports the following serializers:
In node.js environment Wampy is compatible with the following websocket clients:
For convenience, all API documentation is also available as a GitBook here.
import { Wampy } from 'wampy';
const wampy = new Wampy('/ws/', { realm: 'AppRealm' });
try {
await wampy.connect();
} catch (e) {
console.log('connection failed', e);
}
try {
await wampy.subscribe('system.monitor.update', (eventData) => {
console.log('Received event:', eventData);
});
} catch (e) {
console.log('subscription failed', e);
}
try {
const res = await wampy.call('get.server.time');
console.log('RPC called. Server time:', res.argsDict.serverTime);
} catch (e) {
console.log('RPC call failed', e);
}
// Somewhere else for example
await wampy.publish('system.monitor.update');
// or just ignore promise if you don't need it
wampy.publish('client.message', 'Hi guys!');
Wampy.js can be installed using npm:
npm install -S wampy
For browser usage download the latest browser.zip archive and add wampy-all.min.js file to your page. It contains all auth plugins and serializers. They are available as global objects:
window.JsonSerializer = JsonSerializer;
window.MsgpackSerializer = MsgpackSerializer;
window.CborSerializer = CborSerializer;
// WampyCra is not available in the browser bundle due to its dependency on node:crypto
window.WampyCryptosign = wampyCryptosign;
<script src="wampy-all.min.js"></script>
If you don't plan to use other serializers then JSON or any auth plugins, just include wampy.min.js.
<script src="wampy.min.js"></script>
Wampy.js exports the following components that you can import as needed. All exports include TypeScript
type declarations (.d.ts):
{
"exports": {
".": { // Main Wampy class
"types": "./dist/esm/wampy.d.ts",
"import": "./dist/esm/wampy.js",
"require": "./dist/cjs/wampy.cjs"
},
"./JsonSerializer.js": {
"types": "./dist/esm/serializers/json-serializer.d.ts",
"import": "./dist/esm/serializers/json-serializer.js",
"require": "./dist/cjs/serializers/json-serializer.cjs"
},
"./CborSerializer.js": {
"types": "./dist/esm/serializers/cbor-serializer.d.ts",
"import": "./dist/esm/serializers/cbor-serializer.js",
"require": "./dist/cjs/serializers/cbor-serializer.cjs"
},
"./MsgpackSerializer.js": {
"types": "./dist/esm/serializers/msgpack-serializer.d.ts",
"import": "./dist/esm/serializers/msgpack-serializer.js",
"require": "./dist/cjs/serializers/msgpack-serializer.cjs"
},
"./cryptosign.js": { // Cryptosign authentication plugin
"types": "./dist/esm/auth/cryptosign/wampy-cryptosign.d.ts",
"import": "./dist/esm/auth/cryptosign/wampy-cryptosign.js",
"require": "./dist/cjs/auth/cryptosign/wampy-cryptosign.cjs"
},
"./wampcra.js": { // WAMP-CRA authentication plugin
"types": "./dist/esm/auth/wampcra/wampy-cra.d.ts",
"import": "./dist/esm/auth/wampcra/wampy-cra.js",
"require": "./dist/cjs/auth/wampcra/wampy-cra.mjs"
}
}
}
Wampy cli tool exposes almost the same API options to the command line interface. You can use all types of
authorization, publish, subscribe, register and call any URI. Some WAMP Actions provides additional helper options
(e.g. mirror option in register command that allows to return back the invocation payload to the caller).
Cli tool is charged with rich help descriptions, examples and even shell auto-completion script. All parameters may be passed as cmd args or via related ENV Vars for convenience. So you can for example export WAMP Router URI and realm to the environment and provide only wamp action parameters via cmd.
You can install wampy cli tool globally or call it by using npx:
npm install -g wampy
# After that you can invoke wampy
wampy -h
# or just run wampy with npx
npx wampy -h
Check the wampy -h or wampy --help for the available commands and global options and check the help for a specific
command by issuing wampy <call|register|publish|subscribe> -h.
To make use of shell auto-completion features just add output of wampy completion to your shell config:
wampy completion >> ~/.zshrc
# or
wampy completion >> ~/.bashrc
The completion command is hidden from the wampy -h output to not pollute the main use flow as it is only needed
once.
Please refer to Migrating.md for instructions on upgrading major versions.
Below is a description of the exposed public API.
Wampy ships with built-in TypeScript type declarations — no separate @types/wampy package is needed.
Wampy constructor can take 2 parameters:
realm. For node.js environment it's also necessary to specify ws - websocket module. See description below.// in browser
wampy = new Wampy();
wampy = new Wampy('/my-socket-path');
wampy = new Wampy('wss://socket.server.com:5000/ws', { autoReconnect: false });
wampy = new Wampy({ reconnectInterval: 1*1000 });
// in node.js
import { w3cwebsocket as w3cws } from 'websocket';
wampy = new Wampy(null, { ws: w3cws });
wampy = new Wampy('/my-socket-path', { ws: w3cws });
wampy = new Wampy('wss://socket.server.com:5000/ws', { autoReconnect: false, ws: w3cws });
wampy = new Wampy({ reconnectInterval: 1*1000, ws: w3cws });
// or using ws example
import WebSocket from 'ws';
wampy = new Wampy(null, { ws: WebSocket });
wampy = new Wampy('/my-socket-path', { ws: WebSocket });
wampy = new Wampy('wss://socket.server.com:5000/ws', { autoReconnect: false, ws: WebSocket });
wampy = new Wampy({ reconnectInterval: 1*1000, ws: WebSocket });
Json serializer will be used by default. If you want to use msgpack or cbor serializer, pass it through options. Also, you can use your own serializer if it is supported on the WAMP router side.
// in browser
wampy = new Wampy('wss://socket.server.com:5000/ws', {
serializer: new MsgpackSerializer()
});
wampy = new Wampy({
serializer: new CborSerializer()
});
// in node.js
import { Wampy } from 'wampy';
import { MsgpackSerializer } from 'wampy/MsgpackSerializer';
import { CborSerializer } from 'wampy/CborSerializer';
import WebSocket from 'ws';
wampy = new Wampy('wss://socket.server.com:5000/ws', {
ws: WebSocket,
serializer: new MsgpackSerializer()
});
wampy = new Wampy({
ws: w3cws,
serializer: new CborSerializer()
});
Returns Wampy configuration options. See setOptions() down below for the full list of available options.
wampy.getOptions();
Receives a newOptions object as a parameter, where each property is a new option to be set and returns a Wampy instance.
Options attributes description:
false. Enable debug logging.null. User-provided logging function. If debug=true and no logger specified, console.log will be used.true. Enable auto reconnecting. In case of connection failure, Wampy will try to reconnect to WAMP server, and if you were subscribed to any topics, or had registered some procedures, Wampy will resubscribe to that topics and reregister procedures.value: 2000 (ms). Reconnection Interval in ms.25. Max reconnection attempts. After reaching this value .disconnect()
will be called. Set to 0 to disable limit.null. WAMP Realm to join on server. See WAMP spec for additional info.null. Custom attributes to send to router on hello.strict. Can be changed to loose for less strict URI validation.null. Authentication (user) id to use in challenge.[]. Array of strings of supported authentication methods.{}. Additional authentication options for Cryptosign-based authentication.
See Cryptosign-based Authentication section and WAMP Spec CS for more info.{}. Authentication helpers for processing different authmethods flows.
It's a hash-map, where key is an authentication method and value is a function, that takes the necessary user
secrets/keys and returns a function which accepts authmethod and challenge info and returns signed challenge answer.
You can provide your own signing functions or use existing helpers. Functions may be asynchronous.import * as wampyCra from 'wampy/wampcra';
import * as wampyCS from 'wampy/cryptosign';
wampy.setOptions({
authPlugins: {
// No need to process challenge data in ticket flow, as it is empty
ticket: ((userPassword) => (() => userPassword ))(),
wampcra: wampyCra.sign(secret),
cryptosign: wampyCS.sign(privateKey)
},
authMode: 'auto'
});
manual. Possible values: manual|auto. Mode of authorization flow. If it is set
to manual - you also need to provide onChallenge callback, which will process authorization challenge. Or you
can set it to auto and provide authPlugins (described above). In this case the necessary authorization flow
will be chosen automatically. This allows to support few authorization methods simultaneously.null. Callback function.
It is fired when wamp server requests authentication during session establishment.
This function receives two arguments: auth method and challenge details.
Function should return computed signature, based on challenge details.
See Challenge Response Authentication section, WAMP Spec CRA,
Cryptosign-based Authentication section and WAMP Spec CS for more info.
This function receives welcome details as an argument.null. Callback function. Fired on closing connection to wamp server.null. Callback function. Fired on error in websocket communication or if error happens
during auto reconnection flow (as it can not be bound to explicit API calls).null. Callback function. Fired every time on reconnection attempt.null. Callback function. Fired every time when reconnection succeeded.
This function receives welcome details as an argument.null. User provided WebSocket class. Useful in node environment.null. User provided additional HTTP headers (for use in Node.js environment)null. User provided WS Client Config Options (for use in Node.js environment).
See docs for WebSocketClient, tls.connect options.JsonSerializer. User provided serializer class. Useful if you plan to use other encoders
instead of default json.{ json: jsonSerializer }. User provided hashmap of serializer instances for
using in Payload Passthru Mode. Allows to specify a few serializers and use them on per message/call basis.wampy.setOptions({
reconnectInterval: 1000,
maxRetries: 999,
onClose: () => { console.log('See you next time!'); },
onError: () => { console.log('Breakdown happened'); },
onReconnect: () => { console.log('Reconnecting...'); },
onReconnectSuccess: (welcomeDetails) => { console.log('Reconnection succeeded. Details:', welcomeDetails); }
});
Returns the status of last operation. This method returns an object with attributes:
code is integer, and value > 0 means error.error is Error instance of last operation. Check errors types exposed by wampy.reqId is a Request ID of last successful operation. It is useful in some cases (call canceling for example).const defer = wampy.publish('system.monitor.update');
console.log(wampy.getOpStatus());
// may return
// { code: 1, error: UriError instance }
// or { code: 2, error: NoBrokerError instance }
// or { code: 0, error: null }
Returns the WAMP Session ID.
wampy.getSessionId();
Connects to wamp server. url parameter is the same as specified in Constructor.
Returns a Promise that's either:
try {
await wampy.connect();
} catch (e) {
console.log('connection failed', e);
}
await wampy.connect('/my-socket-path');
const defer = wampy.connect('wss://socket.server.com:5000/ws');
Disconnects from wamp server. Clears all queues, subscription, calls. Returns a Promise that's either:
await wampy.disconnect();
Aborts WAMP session and closes a websocket connection.
If it is called on handshake stage - it sends the abort message to wamp server (as described in spec).
Also clears all queues, subscription, calls. Returns wampy instance back.
wampy.abort();
With Ticket-based authentication, the client needs to present the server an authentication ticket -
some magic value to authenticate itself to the server. It could be a user password, an authentication token or
any other kind of client secret. To use it you need to provide "ticket" in "authmethods", "authid" and
the "onChallenge" callback as wampy instance options.
'use strict';
// Ticket authentication
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'joe',
authmethods: ['ticket'],
onChallenge: (method, info) => {
console.log('Requested challenge with ', method, info);
return 'joe secret key or password';
}
});
// Promise-based ticket authentication
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'micky',
authmethods: ['ticket'],
onChallenge: (method, info) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Requested challenge with ', method, info);
resolve('micky secret key or password');
}, 2000);
});
}
});
Wampy.js supports challenge response authentication. To use it you need to provide the "authid" and the "onChallenge"
callback as wampy instance options. Also, Wampy.js supports wampcra authentication method with a little helper
plugin "wampy/wampcra". Just import wampy/wampcra and use provided methods as shown below.
'use strict';
import { Wampy } from 'wampy';
import * as wampyCra from 'wampy/wampcra'; // or import exact functions
import { w3cwebsocket as w3cws } from 'websocket';
// Manual authentication using signed message
wampy = new Wampy('wss://wamp.router.url', {
ws: w3cws, // just for example in node.js env
realm: 'realm1',
authid: 'joe',
authmethods: ['wampcra'],
onChallenge: (method, info) => {
console.log('Requested challenge with ', method, info);
return wampyCra.signManual('joe secret key or password', info.challenge);
}
});
// Promise-based manual authentication using signed message
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'micky',
authmethods: ['wampcra'],
onChallenge: (method, info) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Requested challenge with ', method, info);
resolve(wampyCra.signManual('micky secret key or password', info.challenge));
}, 2000);
});
}
});
// Manual authentication using salted key and pbkdf2 scheme
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'peter',
authmethods: ['wampcra'],
onChallenge: (method, info) => {
const iterations = 100;
const keylen = 16;
const salt = 'password salt for user peter';
console.log('Requested challenge with ', method, info);
return wampyCra.signManual(wampyCra.deriveKey('peter secret key or password', salt, iterations, keylen), info.challenge);
}
});
// Automatic CRA authentication
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'patrik',
authmethods: ['wampcra'],
onChallenge: wampyCra.sign('patrik secret key or password')
});
Wampy.js supports cryptosign-based authentication. To use it you need to provide authid, onChallenge callback
and authextra as wampy instance options. Also, Wampy.js supports cryptosign authentication method
with a little helper plugin "wampy/cryptosign". Just import wampy/cryptosign and use provided methods
as shown below.
The authextra option may contain the following properties for WAMP-Cryptosign:
| Field | Type | Required | Description |
|---|---|---|---|
| pubkey | string | yes | The client public key (32 bytes) as a Hex encoded string, e.g. 545efb0a2192db8d43f118e9bf9aee081466e1ef36c708b96ee6f62dddad9122 |
| channel_binding* | string | no | If TLS channel binding is in use, the TLS channel binding type, e.g. "tls-unique". |
| challenge | string | no | A client chosen, random challenge (32 bytes) as a Hex encoded string, to be signed by the router. |
| trustroot | string | no | When the client includes a client certificate, the Ethereum address of the trustroot of the certificate chain to be used, e.g. 0x72b3486d38E9f49215b487CeAaDF27D6acf22115, which can be a Standalone Trustroot or an On-chain Trustroot |
*: channel_binding is not supported yet. And may be supported only in node.js environment.
'use strict';
import { Wampy } from 'wampy';
import * as wampyCS from 'wampy/cryptosign';
// or you can import only the "sign" method
// import { sign } from 'wampy/cryptosign';
// Manual authentication using signed message
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'joe',
authmethods: ['cryptosign'],
authextra: {
pubkey: '545efb0a2192db8d43f118e9bf9aee081466e1ef36c708b96ee6f62dddad9122'
},
onChallenge: (method, info) => {
console.log('Requested challenge with ', method, info);
return wampyCS.sign('joe secret (private) key')(method, info);
}
});
// Promise-based manual authentication using signed message
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'micky',
authmethods: ['cryptosign'],
authextra: {
pubkey: '545efb0a2192db8d43f118e9bf9aee081466e1ef36c708b96ee6f62dddad9122'
},
onChallenge: (method, info) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Requested challenge with ', method, info);
resolve(wampyCS.sign('micky secret (private) key')(method, info));
}, 2000);
});
}
});
// Automatic CryptoSign authentication
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'patrik',
authmethods: ['cryptosign'],
authextra: {
pubkey: '545efb0a2192db8d43f118e9bf9aee081466e1ef36c708b96ee6f62dddad9122'
},
onChallenge: wampyCS.sign('patrik secret (private) key')
});
If you server provides multiple options for authorization, you can configure wampy.js to automatically choose
required authorization flow based on authmethod requested by server.
For this flow you need to configure the following options:
authid. Authentication id to use in challengeauthmethods. Supported authentication methodsauthextra. Additional authentication optionsauthPlugins. Authentication helpers for processing different authmethods challenge flowsauthMode. Mode of authorization flow. Should be set to autoonChallenge. onChallenge callback. Is not used when authMode=autoimport { Wampy } from 'wampy';
import { sign as CraSign } from 'wampy/wampcra';
import { sign as CryptoSign } from 'wampy/cryptosign';
wampy = new Wampy('wss://wamp.router.url', {
realm: 'realm1',
authid: 'patrik',
authmethods: ['ticket', 'wampcra', 'cryptosign'],
authextra: { // User public key for Cryptosign-based Authentication
pubkey: '545efb0a2192db8d43f118e9bf9aee081466e1ef36c708b96ee6f62dddad9122'
},
authPlugins: {
ticket: ((userPassword) => (() => userPassword ))(),
wampcra: CraSign(userSecret),
cryptosign: CryptoSign(userPrivateKey)
},
authMode: 'auto',
onChallenge: null
});
Subscribes for topicURI events.
Input Parameters:
Returns a Promise that's either:
await wampy.subscribe('chat.message.received', (eventData) => { console.log('Received new chat message!', eventData); });
try {
const response = await wampy.subscribe('some.another.topic',
(eventData) => {
console.log('Received topic event', eventData);
}
);
console.log('Successfully subscribed to topic: ' + response.topic);
} catch (e) {
console.log('Subscription error:' + e.error);
}
Unsubscribe subscription from receiving events.
Parameters:
Returns a Promise that's either:
const f1 = (data) => { console.log('this was event handler for topic') };
await wampy.unsubscribe('subscribed.topic', f1);
const defer = wampy.unsubscribe('chat.message.received');
Publish a new event to topic.
Parameters:
Returns a Promise that's either:
await wampy.publish('user.logged.in');
await wampy.publish('chat.message.received', 'user message'); // will be sent as ['user message1']
await wampy.publish('chat.message.received', ['user message1', 'user message2']);
await wampy.publish('user.modified', { field1: 'field1', field2: true, field3: 123 });
await wampy.publish('chat.message.received', ['Private message'], { eligible: 123456789 });
try {
await wampy.publish('user.modified', { field1: 'field1', field2: true, field3: 123 });
console.log('User successfully modified');
} catch (e) {
console.log('User modification failed', e.error, e.details);
}
Make an RPC call to topicURI.
Parameters:
Returns a Promise that's either:
Important note on progressive call results:
For getting a progressive call results you need to specify progress_callback in advancedOptions.
This callback will be fired on every intermediate result. But the last one result or error
will be processed on promise returned from the .call(). That means that final call result
will be received by call promise resolve handler.
const result = await wampy.call('server.time');
console.log('Server time is ' + result.argsList[0]);
try {
await wampy.call('start.migration');
console.log('RPC successfully called');
} catch (e) {
console.log('RPC call failed!', e.error);
}
try {
await wampy.call('restore.backup', { backupFile: 'backup.zip' });
console.log('Backup successfully restored');
} catch (e) {
console.log('Restore failed!', e.error, e.details);
}
Make an RPC progressive invocation call to topicURI.
Input parameters to this function are the same as described in the call() above. The difference is in the result.
Returns an object containing the result promise and the sendData function
which is supposed to be used to send additional data chunks in bounds of the
initiated remote procedure call:
([payload], [advancedOptions]):
let pc;
try {
pc = ws.progressiveCall('sum.numbers', 1);
} catch (e) {
console.log('RPC call failed!', e.error);
}
// Somewhere where you have additional data to be send to the procedure
try {
pc.sendData(2);
pc.sendData(3);
pc.sendData(4);
// This is final data chunk, so the Callee can understand that
// no more input data will be send and it can finish its
// calculations and send the result.
// Note that nothing stops the Callee to send the
// intermediate results if that makes sense.
pc.sendData(5, { progress: false });
} catch (e) {
console.log('RPC call send data failed!', e.error);
}
// And here we are waiting for the final call result
const res = await pc.result
console.log("Res:", res); // Res: 15
RPC invocation cancelling.
Parameters:
Returns a Boolean or throws an Error:
true if successfully sent canceling messageError if some error occurredconst defer = wampy.call('start.migration');
defer
.then((result) => console.log('RPC successfully called'))
.catch((e) => console.log('RPC call failed!', e));
status = wampy.getOpStatus();
wampy.cancel(status.reqId);
RPC registration for invocation.
Parameters:
Returns a Promise that's either:
Registered PRC during invocation will receive one hash-table argument with following attributes:
{ progress: true } for intermediate results.RPC can return no result (undefined), any single value, array or hash-table object:
"progress": true, which
indicates, that it's a progressive result, so there will be more results in the future.
Be sure to unset "progress" on last result message.const sqrt_f = function (data) { return { result: data.argsList[0]*data.argsList[0] } };
await wampy.register('sqrt.value', sqrt_f);
try {
await wampy.register('sqrt.value', sqrt_f);
console.log('RPC successfully registered');
} catch (e) {
console.log('RPC registration failed!', e);
}
Also, wampy supports rpc with asynchronous code, such as some user interactions or xhr, using promises. For using this functionality in old browsers you should use polyfills, like es6-promise. Check browser support at can i use site.
const getUserName = () => {
return new Promise((resolve, reject) => {
/* Ask user to input his username somehow,
and resolve promise with user input at the end */
resolve({ argsList: userInput });
});
};
wampy.register('get.user.name', getUserName);
Also, it is possible to abort rpc processing and throw error with custom application specific data. This data will be passed to caller onError callback.
Exception object with custom data may have the following attributes:
Note: Any other type of errors (like built in Javascript runtime TypeErrors, ReferenceErrors) and exceptions are caught by wampy and sent back to the client's side, not just this type of custom errors. In this case the details of the error can be lost.
const getSystemInfo = () => {
// Application logic
// for example, if you need to get data from db
// and at this time you can't connect to db
// you can throw exception with some details for client application
const UserException = () => {
this.error = 'app.error.no_database_connection';
this.details = {
errorCode: 'ECONNREFUSED',
errorMessage: 'Connection refused by a remote host.',
database: 'db',
host: '1.2.3.4',
port: 5432,
dbtype: 'postgres'
};
this.argsList = ['Not able to connect to the database.'];
this.argsDict = {};
};
throw new UserException();
};
await wampy.register('get.system.info', getSystemInfo);
try {
await wampy.call('get.system.info');
} catch (error) {
console.log('Error happened', error);
}
RPC unregistration from invocations.
Parameters:
Returns a Promise that's either:
await wampy.unregister('sqrt.value');
try {
wampy.unregister('sqrt.value');
console.log('RPC successfully unregistered');
} catch (e) {
console.log('RPC unregistration failed!', e);
}
During wampy instance lifetime there can be many cases when error happens: some
made by developer mistake, some are bound to WAMP protocol violation, some came
from other peers. Errors that can be caught by wampy instance itself are stored
in opStatus.error, while others are just thrown.
This allows, for example, convenient handling of different types of errors:
import {Wampy, Errors} from 'wampy';
const wampy = new Wampy('/ws/', { realm: 'AppRealm' });
try {
await wampy.call('start.migration');
console.log('RPC successfully called');
} catch (error) {
console.log('Error happened!');
if (error instanceof Errors.UriError) {
// statements to handle UriError exceptions
} else if (error instanceof Errors.InvalidParamError) {
// statements to handle InvalidParamError exceptions
} else if (error instanceof Errors.NoSerializerAvailableError) {
// statements to handle NoSerializerAvailableError exceptions
} else {
// statements to handle any unspecified exceptions
}
}
Wampy package exposes the following Error classes:
For errors attributes look at src/errors.ts file.
Wampy.js supports custom attributes in advancedOptions for protocol extensibility as defined in WAMP specification section 3.1.
Any option matching the pattern _[a-z0-9_]{3,} (starting with underscore, followed by at least 3 alphanumeric characters or underscores) will be passed through as-is to the WAMP router. This allows for custom extensions and router-specific features.
Supported in:
Examples:
// Custom tracking and priority attributes
await wampy.call('api.process.data', { data: 'test' }, {
_tracking_id: 'req_12345',
_priority: 'high',
_custom_auth: 'bearer_token_xyz',
timeout: 5000
});
// Custom routing hints
await wampy.call('distributed.service', payload, {
_route_to_region: 'us-west',
_load_balancer_hint: 'sticky_session',
_retry_policy: 'exponential'
});
Note: Custom attributes are only passed for methods that support them. Standard WAMP options (like timeout, disclose_me, etc.) are handled separately and don't need the underscore prefix.
From v5.0 version there is option to provide custom serializer.
Custom serializer instance must meet a few requirements:
encode (data) method, that returns encoded datadecode (data) method, that returns decoded dataprotocol string property, that contains a protocol name. This name is concatenated with
"wamp.2." string and is then passed as websocket subprotocol http header.isBinary boolean property, that indicates, is this a binary protocol or not.Take a look at json-serializer.ts or msgpack-serializer.ts as examples.
For TypeScript users, the Serializer interface is available in src/serializers/serializer.ts
and can be imported from the package for implementing custom serializers.
Starting from v6.2.0 version you can pass additional HTTP Headers and TLS parameters to underlying socket connection
in node.js environment (thnx websocket library). See example below. For wsRequestOptions you can pass any option,
described in tls.connect options documentation.
import { Wampy } from 'wampy';
import { w3cwebsocket as w3cws } from 'websocket';
const wampy = new Wampy('wss://wamp.router.url:8888/wamp-router', {
ws: w3cws,
realm: 'realm1',
additionalHeaders: {
'X-ACL-custom-token': 'dkfjhsdkjfhdkjs',
'X-another-custom-header': 'header-value'
},
wsRequestOptions: {
ca: fs.readFileSync('ca-crt.pem'),
key: fs.readFileSync('client1-key.pem'),
cert: fs.readFileSync('client1-crt.pem'),
host: 'wamp.router.url',
port: 8888,
rejectUnauthorized: false, // this setting allow to connect to untrusted (or self signed) TLS certificate,
checkServerIdentity: (servername, cert) => {
// A callback function to be used (instead of the builtin tls.checkServerIdentity() function)
// when checking the server's hostname (or the provided servername when explicitly set)
// against the certificate. This should return an <Error> if verification fails.
// The method should return undefined if the servername and cert are verified.
if (servername !== 'MyTrustedServerName') {
return new Error('Bad server!');
}
}
}
});
Wampy.js uses mocha and chai for tests (all written in TypeScript) and c8 for code coverage. Wampy sources are mostly all covered with tests!
# Build the project first (required for CLI tests)
> npm run build
# Run TypeScript type checking
> npm run typecheck
# Run all tests (node + browser-wrappers + karma browser)
> npm test
# Or run specific test suites
> npm run test:node-no-browser-wrappers
> npm run test:node-no-crossbar
> npm run test:browser-wrappers
> npm run test:browser
# Lint the codebase
> npm run lint
# For code coverage report run
> npm run cover
# and then open coverage/lcov-report/index.html
Wampy.js library is licensed under the MIT License (MIT).
Copyright (c) 2014 Konstantin Burkalev
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Thanks JetBrains for support! Best IDEs for every language!
TypeScript
97.8%
JavaScript
2.2%