npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ringcentral-integration/commons

v0.15.0

Published

RingCentral Integration Common Javascript Core Library

Downloads

307

Readme

ringcentral-js-integration-commons

Build Status Coverage Status Codacy Badge NPM Version

Introduction

RingCentral integration common library aims to provide reusable modules to allow developers to integrate RingCentral unified communication service into third party processes or tools more easily.

This project is built based on RingCentral Client and Redux. The basic idea is to wrap RingCentral REST API into highly reusable modules based on common application scenarios and provide an unified application state.

Get Started

To use this library, please follow below steps

Install from NPM

npm install @ringcentral-integration/commons

Create your own Phone object by adding modules

import { combineReducers, createStore } from 'redux';

class Phone extends RcModule {
  constructor() {
    super();
    this.addModule('${module}');
    this._reducer = combineReducers({
      ${moduleName}: this.${module}.reducer,
    });
  }
}
const phone = new Phone();
const store = createStore(phone.reducer);
phone.setStore(store);

Now you are armed with a set of RingCentral services.

Notice: If you have no idea what this section is talking about, which is the case most of the time, please reference next section for more info.

High Level Concept

Module

Module is the basic component, which usually wraps one ore more API calls to provide common used features. A good example to understand module is to compare Call Log related features in RingCentral JS Client and RingCentral Integration Common Library. In RingCentral JS Client, you can get call log with following code

client.account().extension().callLog().list({
	...param
})

There are three kind of modules:

  1. Root module, which provides all other modules for app. Root module also have a duty to provide redux store, this will be discussed more in later sections.
  2. Common modules, which holds a part of functions provided by api.
  3. Custom modules, when common modules can not fulfill your need, you develop one yourself.

Root Module

All needed common modules which are provided by @ringcentral-integration/commons can be listed here. And also other modules composed by you.

import { Alert } from '@ringcentral-integration/commons/modules/Alert';
import { Brand } from '@ringcentral-integration/commons/modules/Brand';

// import other libs
// other variables initialized here

@ModuleFactory({
  providers: [
    { provide: 'Alert', useClass: Alert },
    { provide: 'Brand', useClass: Brand },
    {
      provide: 'SdkConfig',
      useValue: {
        ...apiConfig,
        cachePrefix: `sdk-${prefix}`,
        clearCacheOnRefreshError: false,
      },
    },
    {
      provide: 'ContactSources',
      useFactory: ({ glipContacts }) =>
        [glipContacts],
      deps: ['GlipContacts']
    },
  ]
})
export default class BaseRoot extends RcModule {}

Custom Modules

There are two ways to custom modules:

  1. Extends RcModule and set dependencies by decorators
  2. Extends directly a build-in module
Extends RcModule
@Module({
  deps: [
    'CompanyContacts',
    'GlipPersons',
    { dep: 'GlipContactsOptions', optional: true }
  ]
})
export default class GlipContacts extends RcModule {
}
Extends Build-in Module
@Module({
  deps: []
})
export default class NewGlipGroups extends GlipGroups {
}

Module Providers

Two steps to set providers for root module.

Step 1. Set common moduels


@ModuleFactory({
  providers: [
    { provide: 'Alert', useClass: Alert },
    { provide: 'Brand', useClass: Brand },
  ]
})
export default class BaseRoot extends RcModule {
  initialize() {
    // ...initialize stuff here
  }
}

Step 2. Set self composed modules by HOC

function createRootModule({ apiConfig, redirectUri }) {
  @ModuleFactory({
    providers: [
      {provide: 'SDKConfig', useValue: {...apiConfig, cachePrefix: `sdk-${prefex}`}},
      { provide: 'OAuthOptions', useValue: { redirectUri } },
    ]
  })
  class Root extends RcModule {}

  return Root.create();
}
How It Works

There's no action creator in modules. All actions are in an Enum instance.

RcModule's default implementation included a method: initialize(), it's definition is:

  initialize() {
    this.store.subscribe(() => this._onStateChange());
  }

Generally you dont have to do any thing to change this implementation. Just get your _onStateChange method done.

  _onStateChange() {
    if (this._shouldInit()) {
      this.store.dispatch({
        type: this.actionTypes.initSuccess,
      });
    } else if (this._shouldReset()) {
      this.store.dispatch({
        type: this.actionTypes.resetSuccess,
      });
    }
  }

_shouldInit() checks if all dependencies are initialized and when all done, send an initSuccess action. This action will inform the store that your own module is initialized successfuly. Although the action is named initSuccess but it has it's prefix, which is your module's name or something else you specified.

_shouleReset() is the same.

Selector

Get what you want from state.

Module Store

  1. Store can only be set to Root Module.
  2. Store can noly be set once
  setStore(store) {
    if (this._modulePath !== 'root') {
      throw new Error('setStore should only be called on root module');
    }
    if (!store) {
      throw new Error('setStore must accept a store object');
    }
    if (this._store) {
      throw new Error('setStore should only be called once');
    }
    this._setStore(store);
    this._initModule();
  }

  _setStore(store) {
    this._store = store;
    for (const subModule in this) {
      if (
        Object.prototype.hasOwnProperty.call(this, subModule) &&
          this[subModule] instanceof RcModule
      ) {
        this[subModule]._setStore(store);
      }
    }
  }

setStore methods also triggers module initialization.

  1. All sub modules' _setStore methods are called too.
  2. All sub modules. And in every module, the initizlize methods is called.
  setStore(store) {
    // ...
    this._setStore(store);
    this._initModule();
  }

    _initModule() {
    if (
      !this._suppressInit &&
      !this._initialized
    ) {
      this._initialized = true;
      this.initialize();
    }

    for (const subModule in this) {
      if (
        Object.prototype.hasOwnProperty.call(this, subModule) &&
          this[subModule] instanceof RcModule
      ) {
        this[subModule]._initModule();
      }
    }
  }

  initialize() {
    this.store.subscribe(() => this._onStateChange());
  }

In initialize method, it may subscribe the store to state change.

Module's create method

  1. Get all modules' value (if this module is ValueProvider) or instances. In this step. all providers are resolved and set them to Root Module instance, key is the provider's token and instance is the provider's instance.
  2. Get all sub modules' reducers, combine them then set it to Root Module's _reducer field.

Phone

Phone is an aggregator of modules which provides a full functional object in application level. As different application requires different features, the Phone object needs to be constructed in application level by adding required modules. A typical way to create Phone object is something like below

class Phone extends RcModule {
  constructor() {
    super();
    this.addModule('${module}');
    this._reducer = combineReducers({
      ${moduleName}: this.${module}.reducer,
    });
  }
}
const phone = new Phone();

Store

As Phone object is built up with Redux, after Phone object is created, you need to use following code to create Redux store

const store = createStore(phone.reducer);
phone.setStore(store);

Action

Actions are defined with ObjectMap class, which will be discussed in next section.

export const ObjectMap.prefixKeys([
  ...ObjectMap.keys(moduleActionTypes),
  'alert',
  'dismiss',
  'dismissAll',
], 'alert');

The prefixKeys function is a factory function to create ObjectMap instances. The instance will have the object shape of:

{
    alert: 'alert-alert',
    dismiss: 'alert-dismiss',
    dismissAll: 'alert-dismissAll',
    [key in moduleActionTypes]: `alert-${moduleActionTypes[key]}`,
}

You can use it like:

export function getMessagesReducer(types) {
  return (state = [], type) => {
    switch (type) {
      case types.alert:
        return [
          ...state,
          {
            // ...
          },
        ];
      case types.dismiss:
        return // state
      case types.dismissAll:
        return [];
      default:
        return state;
    }
  };
}

Please notice getMessagesReducer is a high order function. It will return a function which is the reducer you want.

Every module has its needed actions defined within the module with Enum.

ObjectMap

ObjectMap is a class representation of a read-only dictionary implemented with an object mapping. There are several uses of ObjectMaps:

  1. Define a lookup table with an object definition:
const table = ObjectMap.fromObject({
  foo: 'bar',
  alpha: 1,
  beta: 2,
  isObject: true,
} as const);
// with as const in typescript, the instance will clearly show the correct key-value relationship in its shape
  1. Define a set of values by using the value as the key:
const callResults = ObjectMap.fromKeys([
  'hangUp',
  'missed',
  'voicemail',
  'completed',
]);
/* The shape of the object would be as the following, with complete key-value shape in the typescript typing system.
{
    hangUp: 'hangUp',
    missed: 'missed',
    voicemail: 'voicemail',
    completed: 'completed',
}
*/
  1. Prefix a set of values:
const actionTypes = ObjectMap.prefixKeys([
    'init',
    'initSuccess',
    'reset',
    'resetSuccess'
], 'authModule');
/* The shape of the object would be the following:
{
    init: 'authModule-init',
    initSuccess: 'authModule-initSuccess',
    reset: 'authModule-reset',
    resetSuccess: 'authModule-resetSuccess',
}

However, in the typing system, the shape of the object is:
{
    init: string;
    initSuccess: string;
    reset: string;
    resetSuccess: string;
}

DI

  1. Module decorator ==> registerModule
  2. Lib decorator ==> registerModule
  3. ModuleFactory decorator ==> registerModuleFactory

What's in common is that all these registered modules are stored in a map instance with the Class as key and metadata as the value.

When does DI work is when Root Module called create() method. In the method, Injector class started to handle those Module, Library and ModuleFactory decorator and to make dependency injection by its addModule method .

In the same time, modules' reducers and state are also initialized and set to module by the methods prefixed by _.

Module

When one module has dependencies on other modules.

How to use:

@Module({
  deps: [{ dep: 'AlertOptions', optional: true }]
})
export default class Alert extends RcModule {
  // ..
}

In the map, key is `Alert` class and value is `{ dep: 'AlertOptions', optional: true }.

Provider

ModuleFactory

This decorator can be used on any kind modules, no matter it's root module or not.

Dependency Injection

Please refer to Dependency Injection for more details.

Contribution


Please fork the project and read the following: