@devops_coin/coin-sdk

COIN SDK for Nodejs

Usage no npm install needed!

<script type="module">
  import devopsCoinCoinSdk from 'https://cdn.skypack.dev/@devops_coin/coin-sdk';
</script>

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 file
    • sharedkey.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 the MessageType-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 the IOffsetPersister interface. The purpose of this interface is to track the message-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 given message-id offset. When using ConfirmationStatus.UNCONFIRMED the offset is in most cases not very useful. The ConfirmationStatus.ALL filter is better. Note: it is the responsibility of the client to keep track of the offset.

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

References