# ringcentral-js-integration-commons

[![Build Status](https://travis-ci.org/ringcentral/ringcentral-js-widgets.svg?branch=master)](https://travis-ci.org/ringcentral/ringcentral-js-widgets)
[![Coverage Status](https://coveralls.io/repos/github/ringcentral/ringcentral-js-widgets/badge.svg?branch=master)](https://coveralls.io/github/ringcentral/ringcentral-js-widgets?branch=master)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/39f7f85c990b4eeab98702c89cdd31d3)](https://www.codacy.com/app/RingCentral/ringcentral-js-widgets?utm_source=github.com&utm_medium=referral&utm_content=ringcentral/ringcentral-js-widgets&utm_campaign=badger)
[![NPM Version](https://img.shields.io/npm/v/@ringcentral-integration/commons.svg?style=flat-square)](https://www.npmjs.com/package/@ringcentral-integration/commons)


## 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](https://www.npmjs.com/package/ringcentral-client) and [Redux](https://github.com/reactjs/redux). The basic idea is to wrap [RingCentral REST API](https://developer.ringcentral.com/) 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

```bash
npm install @ringcentral-integration/commons
```

Create your own Phone object by adding modules

```javascript
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

```javascript
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.
```javascript
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
```javascript
@Module({
  deps: [
    'CompanyContacts',
    'GlipPersons',
    { dep: 'GlipContactsOptions', optional: true }
  ]
})
export default class GlipContacts extends RcModule {
}
```

##### Extends Build-in Module
```javascript
@Module({
  deps: []
})
export default class NewGlipGroups extends GlipGroups {
}
```

#### Module Providers
Two steps to set providers for root module.

Step 1. Set common moduels
```javascript

@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
```javascript
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:
```javascript
  initialize() {
    this.store.subscribe(() => this._onStateChange());
  }
```
Generally you dont have to do any thing to change this implementation. Just get your `_onStateChange`
method done.

```javascript
  _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

```javascript
  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.

```javascript
  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

```javascript
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

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

### Action
Actions are defined with ObjectMap class, which will be discussed in next section.
```javascript
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:
```javascript
{
    alert: 'alert-alert',
    dismiss: 'alert-dismiss',
    dismissAll: 'alert-dismissAll',
    [key in moduleActionTypes]: `alert-${moduleActionTypes[key]}`,
}
```

You can use it like:
```javascript
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:
```typescript
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
```

2. Define a set of values by using the value as the key:
```typescript
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',
}
*/
```

3. Prefix a set of values:
```typescript
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:
```javascript
@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](docs/dependency-injection.md) for more details.

## Contribution
---

Please fork the project and read the following:

- [Contribution Guide](docs/contribute.md)

