rokot-notification

Rokot - Rocketmakers TypeScript NodeJs Platform

Usage no npm install needed!

<script type="module">
  import rokotNotification from 'https://cdn.skypack.dev/rokot-notification';
</script>

README

rokot-notification

Rokot - Rocketmakers TypeScript NodeJs Platform

Introduction

The Rokot Notification library provides a framework to create (JSON) Notification Messages and send them to users via a notification channel (email, apns and gcm 'out-the-box')

These notification messages are generally transcoded into some form of textual content (HTML/text), but could also directly contain the content to be sent

Core Components

INotification

The library is based around the core concept of a notification message (INotification)

This should represent the real life data behind the user notification

Each notification should have:

  1. a unique type (this is the key to indicate which handler will process the notification)
  2. a recipient to indicate the audience of the notification - which can be string values (for use in a single transport scenario), or transport keys {email:"developer@code.com", apns:["ABC", "XYYZ"], gcm:"xds") to support multiple transports and multiple tokens
import {INotification} from "rokot-notification";

export interface ISampleTemplateNotification extends INotification {
  fail?: boolean;
}
const notification: ISampleTemplateNotification = {
  type: "sample-underscore-template-message",
  recipient: {
    email:"developers@rocketmakers.com"
  },
  fail: false
}

INotificationHandler

Each notification type requires a notification handler

The framework provides this handler with decorators to add functions to support:

  1. validating notifications before the transport method is called
  2. creating template content (html/text content via underscore templates)
  3. to pre cache the templates at startup (loading template content from disk/db early)
  4. to validate the template execution at startup with test case notifications you provide

A simple notification handler that just utilise the transport

import {notifications, INotification} from "rokot-notification";
import {INodemailerNotificationHandler, INodemailerNotificationTransport} from "rokot-notification/lib/nodemailer";

/** Define the shape of the Notification */
export interface ISimpleNotification extends INotification {
  html: string;
  text: string;
}

/**
Register this handler for the notification type via @notification.handler("simple")
NOTE: You dont need to export the notification handler
*/
@notifications.handler("simple")
class SimpleHandler implements INodemailerNotificationHandler<ISimpleNotification>{

  /**
  register the nodemailer transport function via @notifications.transport()
  */
  @notifications.transport()
  async nodemailer(notification: ISimpleNotification, transport: INodemailerNotificationTransport) {
    return await transport.send(notification.recipient, m => {
      m.from = transport.getFromAddress(notification)
      m.subject = `Simple Email`
      m.text = notification.text
      m.html = notification.html
    })
  }
}

Or a complete example showing usage of all the functions

import {notifications, UnderscoreFileSystemTemplateProcessorFactory, INotification} from "rokot-notification";
import {INodemailerNotificationHandler, INodemailerNotificationTransport} from "rokot-notification/lib/nodemailer";
import {ISampleTemplateNotification} from "./sampleTemplateNotification";
import {emailTo} from "../../settings";

/** The example templates processor factory */
const exampleTemplatesFactory = new UnderscoreFileSystemTemplateProcessorFactory("./source/test/examples/templates")

export const notificationType = "sample-underscore-template-message"

/** You dont need to export the notification handler*/
@notifications.handler(notificationType)
class TemplateHandler implements INodemailerNotificationHandler<ISampleTemplateNotification>{

  /** Validate the notification before its passed to the transport method */
  @notifications.validator()
  validate(notification: ISampleTemplateNotification){
    return Promise.resolve(notification);
  }

  /** Provides a sample notification that will be validated against any @notification.template() functions */
  @notifications.testCase()
  testMessage() : ISampleTemplateNotification{
    return {type: notificationType, recipient: {nodemailer: emailTo}, fail: true}
  }

  /** You can provide as many test cases as you like (to exercise the template generation) */
  @notifications.testCase()
  testMessage1() : ISampleTemplateNotification{
    return {type: notificationType, recipient: {nodemailer: emailTo}, fail: false}
  }

  /** You can also inspect which @notification.template() function is requesting the data */
  @notifications.testCase()
  testMessage2(template: string) : ISampleTemplateNotification{
    if (template === "text") {
      // dont use this for the 'text' template
      return
    }
    return {type: notificationType, recipient: {nodemailer: emailTo}}
  }

  /**
  Using Underscore templates loaded from a root folder
  These template providing function are pre-cached at startup to reduce runtime IO
  */
  @notifications.template()
  html(){
    return exampleTemplatesFactory.create<ISampleTemplateNotification>(`${notificationType}.html`)
  }

  /** Using Underscore templates loaded from a root folder */
  @notifications.template()
  text() {
    return exampleTemplatesFactory.create<ISampleTemplateNotification>(`${notificationType}.txt`)
  }

  /** the nodemailer transport function */
  @notifications.transport()
  async nodemailer(notification: ISampleTemplateNotification, transport: INodemailerNotificationTransport) {
    return await transport.send(notification.recipient, m => {
      m.from = transport.getFromAddress(notification)
      m.subject = `Template Email`
      // using templates.transform provides access to the pre-cached @notification.template() decorated function results
      m.text = notifications.transformTemplate(notification, this.text)
      m.html = notifications.transformTemplate(notification, this.html)
    })
  }
}

Or a notification handler using multiple transports

import {notifications, INotification} from "rokot-notification";
import {INodemailerNotificationHandler, INodemailerNotificationTransport} from "rokot-notification/lib/nodemailer";
import {IApnsNotificationHandler, IApnsNotificationTransport} from "rokot-notification/lib/apns";

export interface IMultipleNotification extends INotification {
  type: "sample-multi-channel-message"
  count: number;
}

@notifications.handler("sample-multi-channel-message")
class MultipleHandler implements IApnsNotificationHandler<IMultipleNotification>, INodemailerNotificationHandler<IMultipleNotification>{

  /** the apns transport function */
  @notifications.transport()
  async apns(notification: IMultipleNotification, transport: IApnsNotificationTransport) : Promise<any>{
    return await transport.send(notification.recipient, n => {
      n.body = `Count of ${notification.count}`
      n.topic = "<app package id>"
    })
  }

  /** the nodemailer transport function */
  @notifications.transport()
  async nodemailer(notification: IMultipleNotification, transport: INodemailerNotificationTransport) {
    return await transport.send(notification.recipient, m => {
      m.from = transport.getFromAddress(notification)
      m.subject = `Test Email`
      m.text = `Count of ${notification.count}`
      m.html = `Test HTML email`
    })
  }
}

NOTE: There are 3 core notification handlers avaiable 'out-the-box' IApnsNotificationHandler<TNotification>, IGcmNotificationHandler<TNotification> and INodemailerNotificationHandler<TNotification>

NotificationDispatcher

The framework provides a NotificationDispatcher to route notifications to their dispatch handler

You need to create your Application NotificationDispatcher and configuring which transports are used

import {NotificationDispatcherFactory} from "rokot-notification";
import {apnsInitializer, IApnsProviderOptions} from "rokot-notification/lib/apns";
import {sendgridInitializer,INodemailerSendgridOptions} from "rokot-notification/lib/nodemailer";
import {gcmInitializer,IGcmProviderOptions} from "rokot-notification/lib/gcm";
import {ConsoleLogger, Logger} from "rokot-log";

async function createDispatcher(nodemailerOptions: INodemailerSendgridOptions, apnsOptions: IApnsProviderOptions, gcmOptions: IGcmProviderOptions) {
  const logger = ConsoleLogger.create("test-multiple-message-dispatcher")
  const dispatcherFactory = new NotificationDispatcherFactory(logger)
  return await dispatcherFactory.createDispatcher(
    sendgridInitializer(logger, nodemailerOptions),
    gcmInitializer(logger, gcmOptions),
    apnsInitializer(logger, apnsOptions));
}

// createDispatcher({username:"sendgrid username", password:"send grid password"}, {token:{key:"/path/to.p8",keyId:"KEY_ID",teamId:"TEAM_ID"},production:false}, {apiKey:"<gcm api key>"})

NOTE: You can create your own MessageHandler for any kind of notification channel you like, or change the Nodemailer transport (from the 'out-the-box' sendgrid)

Notification Client Generation

You can create a typescript file in the root of your source and include the source below (modifying the paths to match your handler locations)

This code will inspect the source code and generate a Notifications client - exposing methods for each of the notification types

import {ConsoleLogger} from "rokot-log";
import {generateClient, getInterfaceFunctionName, getFolderFiles} from "rokot-notification/lib/client/generator";
const logger = ConsoleLogger.create("create notification client",{level:"info"})

/** Get all notification handler source files (all in a single folder) */
const files = getFolderFiles("./source/test/examples/handlers");

/** Generate a notification client */
generateClient(logger, files, "./source/test/examples/notifications.ts", {
  /** override the method naming function */
  functionName: getInterfaceFunctionName
})

Templates

HTML Template

<div>
Hello There <%= model.recipient.nodemailer || model.recipient.email || model.recipient %> (<%= model.type %>)
Process <%= model.fail ? "Failed" : "Ok" %>
</div>

Getting Started

Installation

Install via yarn

yarn add rokot-notification

Further Examples

Check out the examples in /package/source/test/examples to see various dispatcher configs and message handlers

Consumed Libraries

node-gcm

Android Push Notifications

apn

Apple Push Notifications

nodemailer

Email Notifications

nodemailer-sendgrid-transport

Email via sendgrid transport

rokot-test

The testing framework used within the Rokot Platform!