README
COIN SDK Nodejs
The Vereniging COIN supplies third parties with an SDK for Node.js that supports secured access to the Number Portability API.
Installation
First of all you will need to have the latest version of Node installed on your machin, so execute the following commands:
$ curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
$ sudo apt-get install -y nodejs
Insure that you have the correct versions of node and npm installed:
$ node -v
v10.16.0
$ npm -v
6.9.0
The SDK is available as a NPM package. You can download and install the SDK as follows:
$ $ npm install @devops_coin/coin-sdk
Settings
We use the dotenv library for adjusting the application settings.
$ cat env.example
BASE_URL=https://test-api.coin.nl
CONSUMER_NAME=<<FILL IN THE CONSUMER NAME>>
You need to copy the env.example
file to .env
and make needed changes which will be used by the application.
In order to use this settings library, be sure that you add the following at the top of your file:
const env = require('@devops_coin/coin-sdk/common-sdk/env');
env.init();
Security
See the jsrsasign library.
Building pattern
See the Building objects progressively with the builder pattern in javascript article.
Streams
See the EventSource library.
Configure Credentials
For secure access various credentials are required.
- For how to configure credentials see README introduction
- To summarize, you will need:
- Consumer name
private-key.pem
filesharedkey.encrypted
encrypted (by public key) HMAC secret file
Add the following properties to the .env file
# Name of the consumer as configured in: TEST - https://test-portal.coin.nl/iam#/ or PROD - https://portal.coin.nl/iam#/
CONSUMER_NAME=<your-consumer-name>
# Path to the private key file (public key is registerd in the COIN IAM Portal)
PRIVATE_KEY_FILE=path-to/private-key.pem
#Path to encrypted HMAC secret for given consumer as copied from the COIN IAM Portal (links see above)
SHARED_SECRET_FILE=path-to/sharedkey.encrypted
Message Types
The Number Portability SDK can send the following functional messages:
- Porting Messages
- Service Number Messages
- ENUM Messages
Sending Messages
The SDK provides various message builders for creating messages to send by means of the 'NumberPortabilityService' service.
To finish a porting request successfully, the following messages should be sent:
- A Porting Request by the Recipient
- A Porting Request Answer by the Donor
- A Porting Performed by the Recipient
For each of these three messages a Builder exists. It is also possible to create the message objects by JavaScript objects.
Porting Request
The construction of a Porting Request Message can be done in the following manner:
const PortingRequestBuilder = require('@devops_coin/coin-sdk/number-portability-sdk/messages/v3/builder/PortingRequestBuilder');
const builder = new PortingRequestBuilder();
const message = builder
.setFullHeader(senderNetworkOperator, senderServiceProvider, 'CRDB', null)
.setDossierId(dossierId)
.setRecipientNetworkOperator(senderNetworkOperator)
.addPortingRequestSequence()
.setNumberSeries(startNumberRange, endNumberRange)
.finish()
.build();
It is possible to add additional Number Series by repeating the adding of the PortingRequestSequence and concluding it by calling the finish() method.
At the end the full message is built by calling the build()
method.
const message = builder
.setFullHeader(senderNetworkOperator, senderServiceProvider, 'CRDB', null)
.setDossierId(dossierId)
.setTimestamp(timestamp)
.setRecipientNetworkOperator(senderNetworkOperator)
.addPortingRequestSequence()
.setNumberSeries(startFirstNumberRange, endFirstNumberRange)
.finish()
.addPortingRequestSequence()
.setNumberSeries(startSecondNumberRange, endSecondNumberRange)
.finish()
.build();
Sending Messages
The sending of messages can be done in the following manner:
const NumberPortabilityService = require('@devops_coin/coin-sdk/number-portability-sdk/messages/v3/api/service');
this.service.sendMessage(message).then(function(result) {
const response = MessageResponse.constructFromObject(result, {});
console.log(response.transactionId);
});
Consume Messages
Create Message Listener
For message consumption, the number portability API makes use of HTTP's ServerSentEvents. A message listener is needed which will be triggered upon reception of a message payload. Whenever the API doesn't send any other message for 20 seconds, it sends an empty 'heartbeat' message, which triggers the listener's onKeepAlive() method.
class MessageListenerExample {
onActivationsn(message) {
const activationSnMessage = ActivationServiceNumberMessage
.constructFromObject(JSON.parse(message.data).message);
console.log(`${message.lastEventId} - ${JSON.stringify(activationSnMessage)}`);
// Business logic to process this messages should be added here
}
onCancel(message) {
const cancelMessage = CancelMessage
.constructFromObject(JSON.parse(message.data).message);
console.log(`${message.lastEventId} - ${JSON.stringify(cancelMessage)}`);
// Business logic to process this messages should be added here
}
// ...
onTariffChangeSn(message) {
const tariffChangeServiceNumberMessage = TariffChangeServiceNumberMessage
.constructFromObject(JSON.parse(message.data).message);
console.log(`${message.lastEventId} - ${JSON.stringify(tariffChangeServiceNumberMessage)}`);
// Business logic to process this messages should be added here
}
onKeepAlive() { // optional
// called when a keep alive message arrives
}
onUnknownMessage(message) { // optional
// called when an unknown message arrives
}
}
Start consuming messages
Here is an example to start consuming messages:
const NumberPortabilityMessageConsumer = require('@devops_coin/coin-sdk/number-portability-sdk/messages/v3/api/consumer');
this.consumer = new NumberPortabilityMessageConsumer(consumerName, privateKeyFile, encryptedHmacSecretFile, \
baseUrl, 30, 10, validPeriodInSeconds);
const options = {
confirmationStatus: ConfirmationStatus.UNCONFIRMED, // optional. UNCONFIRMED is default. Use ALL to retrieve all messages since an offset.
offsetPersister: new OffsetPersister(), // required when confirmationStatus is ALL
messageTypes: [...] // optional. Default is all messageTypes. See MessageTypeEnum
};
const disposable = this.consumer.startConsuming(new MessageListenerExample(), options);
disposable.promise.then(()=>{ console.log("stream has closed"); }, (error)=>{ console.error(error); });
// The statement below will close the stream immediately, it can be used to close the stream if necessary
// For test purposes it can be used with a timeout.
// disposable.close(); // resolves disposable
Consume specific messages using filters
The NumberPortabilityMessageConsumer
provides various filters for message consumption. The filters are:
MessageType
: All possible message types, including errors. Use theMessageType
-enumeration to indicate which messages have to be consumed.- ConfirmationStatus:
ConfirmationStatus.UNCONFIRMED
: consumes all unconfirmed messages. Upon (re)-connection all unconfirmed messages are served.ConfirmationStatus.ALL
: consumes confirmed and unconfirmed messages.
Note: this filter enables the consumption of the whole message history. Therefore, this filter requires you to supply an implementation of theIOffsetPersister
interface. The purpose of this interface is to track themessage-id
of the last received and processed message. In the case of a reconnect, message consumption will then resume where it left off.
offset
: starts consuming messages based on the givenmessage-id
offset. When usingConfirmationStatus.UNCONFIRMED
theoffset
is in most cases not very useful. TheConfirmationStatus.ALL
filter is better. Note: it is the responsibility of the client to keep track of theoffset
.
The message consumer will try to connect up to listener.numberOfRetries
times to the number portability API.
Confirm Messages
Once a consumed message is processed, it needs to be confirmed. To confirm a message use the NumberPortabilityService.sendConfirmation(id)
method:
onPortingRequest(message) {
const portingRequest = PortingRequestMessage.constructFromObject(JSON.parse(message.data).message);
console.log(`${message.lastEventId} - ${JSON.stringify(portingRequest)}`);
this.service.sendConfirmation(message.lastEventId).then(function(result) {
const response = {};
MessageResponse.constructFromObject(result, response);
console.log(response.transactionId);
});
}