# @articulatesydney/gmservice

> GM-SERVICE ====

Latest version **2.1.1** (published 2018-11-19) · 0 weekly downloads

## Install

```sh
npm install @articulatesydney/gmservice
pnpm add @articulatesydney/gmservice
yarn add @articulatesydney/gmservice
bun add @articulatesydney/gmservice
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 2.1.1 |
| Published | 2018-11-19 |
| First published | 2018-09-24 |
| Weekly downloads | 0 |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 2 |
| Unpacked size | 664.1 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Maintainers | francox |

## Links

- npm: https://www.npmjs.com/package/@articulatesydney/gmservice
- npm.io page: https://npm.io/package/@articulatesydney/gmservice

## Dependencies (2)

- [rx-lite](https://npm.io/package/rx-lite.md) ^4.0.8
- [is-mobile](https://npm.io/package/is-mobile.md) ^2.0.0

## Recent versions

- 2.1.1 (latest) — 2018-11-19
- 2.1.0 — 2018-10-24
- 2.0.0 — 2018-10-19
- 1.0.0 — 2018-09-24

## README

GM-SERVICE
====

# About
本JS库目的在于管理游戏列表 主要特性有
1. 提供常用的异步数据支持
2. 封装常用的展示游戏列表的方式
3. 基于rxjs 非常extendable

GMService基于rxjs，数据交互操作全部为异步实现。 获取数据的接口的使用方式基本上都是
```javascript
instance.someapi.subscribe(data => {
    //...
})
```
[完整API列表]()


# 如何使用

## 安装
```
npm install --save @articulatesydney/gmservice
```
或者直接在html中加入
```
 <script src="....bundle.js"></script>
```

## 测试
```
npm run test
```

## Build
```
npm run build
```

## 使用
1. 设置参数并初始化gmservice 得到gmservice的instance
```javascript
var instance = new gmservice({
    games: GAMELISR,
    cache: 10000,
    launchGame: function (game, { resolve }) {
        // ... resolve(xxxx)
    }
})
```

2. 从instance提供的api中获取数据

 获取provider
```javascript
instance.providers.subscribe(providers => {
    console.log(providers);
})
```

 获取完整游戏列表
```javascript
instance.list(games => {
    console.log(games);
})
```

## Tips
1. 请避免使用fat arrow functions(transform函数以及process函数除外)
2. 在gallery中使用transform.order的时候, GALLERY_FORMAT.PROVIDERS_OF_CATEGORIES模式中将会先调用category的order方法；对于每个category内部的providers的顺序会由groups order决定（example请见测试用例以及example lobby.html）

# Configuration

gmservice内部默认认为传入的游戏列表为标准格式,
可以在games中直接传入标准格式的游戏列表；也可以使用rawProcess函数进行

标准格式包含以下property

1. cn_name: string
2. en_name: string
3. image: string
4. code: string
5. provider: string
6. category:string
7. types: object - 这个设计是为了实现一个游戏可以同时有多个类型的需求
```javascript
{
    slot: true,
    jackpot: true,
    asian: true
}
```
8. extra: object - 用于记录额外的数据：可能在自定义的搜索中使用


## 配置列表


```javascript
var instance = new gmservice({
    
    /**
    * 1. games
    * - 如果网页版游戏列表与h5有出入, 具体如何加载合适的游戏列表需要使用者自己处理
    * 
    * format: 
    *  - array of games OR
    *  - function
    *    主要用于异步加载游戏列表：在load完成后调用resolve
    *  
    */
    games: gamelist,
    games: (resolve) => {
        fetch('/gamelist')
        .then(d => d.json())
        .then(games => {
            resolve(games)
        })

        /*
        games: (resolve) => resolve(gamelist)
        与
        games: gamelist
        等价
        */
    }

    /**
     * 2. providers 
     * - 在游戏列表上筛选特定平台的游戏
     * - 主要用于在cache了完整的游戏列表的环境中 只向玩家显示特定平台的相关游戏数据
     * 
     * **如果游戏列表中本来就没有给定平台的游戏 也不会造成多余的显示
     * 
     * providers format
     * - array of provider names
     * - function
     * 
    */
    providers: ['PNG', 'MG']
    providers: (resolve) => {
        fetch('/valid_providers')
        .then(d => d.json())
        .then(providers => resolve(providers));
    },

    /**
     * 3. categories
     * - 在游戏列表上筛选特定类型的游戏
     * - 主要用于在cache了完整的游戏列表的环境中 只向玩家显示特定类型的相关游戏数据
     * 
     * **如果游戏列表中本来就没有给定平台的游戏 也不会造成多余的显示
     * 
     * categories format
     * - array of provider names
     * - function
     * 
    */
    categories: ['slots', 'live']
    categories: (resolve) => {
        fetch('/valid_categories')
        .then(d => d.json())
        .then(categories => resolve(categories));
    },

    /**
     * 4. filter - function
     * - 在游戏列表上自定义筛选游戏
     * - 主要用于根据特殊条件过滤游戏： 比如说 不希望玩家看到 rtp超过95%的游戏；
     * 或者不希望玩家看到PNG的带有slot字样的游戏
     * 
     * filter format
     * - function
     * 
    */
    filter: (game) => {
        return game.extra.rtp < 0.95;
        // return game.provider !== 'PNG' || game.en_name.search('slots') < 0
    }

    /**
     * 5. rawProcess - function
     * - 用于对游戏列表进行预处理 以转换至标准格式
     * 
    */
    rawProcess: function (g) {
        return Object.assign(g, {
            code: g.game_code,
            cn_name: g.name,
            en_name: g.english_name,
            types: (g.game_type || '').split('_').reduce((prev, t) => ({ ...prev, [t]: true }), {}),
            extra: {}
        })
    },

    /**
     * 6. injectUpdates - function
     * 
     * function injectUpdates({gamelist, cache_updated_at, cache_expire_at}, resolveFn): void
     * 
     * - 检查游戏列表是否有更新：如果有的话将新的数据纳入系统
     * - 新的数据不会被rawProcess处理（因为可能还有updated/removed games的情况，injectUpdates用来处理一些非常灵活的情形）
     * - 如果同时还开启了cache 新的数据以及当前的timestamp将被写入cache；做为下次调用时的cache_updated_at
     * 
     * - 一个特别的使用例子：在lobby上使用时 推荐cache设置为999999999: 即使有cache也会检查更新，而且更新的timestamp会被写在cache中
     * 
    */
    injectUpdates: ({gamelist, cache_updated_at, cache_expire_at}, resolve) => {

        fetch(`/new_games?updated_at=${cache_updated_at}`)
        .then(d => d.json())
        .then((newgames = []) => {
            resolve([...gamelist, newgames]);
        })
    },


    /**
     * 7. cache - 
     * - 保存处理完成之后的游戏列表
     * - 如果有合法的cached游戏列表存在的话 将不会使用games参数传入的游戏列表或者function
     * - 网页版与h5版的游戏列表将分别cache
     * - 
     * 
     * format：
     *  - number - 浏览器中使用localStorage: 在浏览器中保存
     *  - string: 'session' - 浏览器中使用 sessionStorage： 本次tab session中cache游戏列表
     * 
    */
    cache: 1000 * 60 * 60 * 24 * 7, // 1 week
    cache: 'session',


    /**
     * 8. recommends: 
     * - 接受的数据为一个array：将index=0的数据视为全局推荐游戏列表
     *  index=1的数据视为对应agent的推荐游戏列表
     * - 为了解决inconsistancy 推荐游戏列表实际上提供一些标准 还是会从gamelist中去匹配合适的数据；每个object只需要但必须要提供provider,category以及code
     * 
     * format：
     *  - array of gamelist arrays [gamelist1, gamelist2]
     *  - function
     * 
    */
   recommends: [general_recommends, operator_recommends],
   recommends: (resolve) => {
       fetch('/some-recommends?playerid=111')
       .then(d => d.json())
       .then(data => resolve(data))
   },

   /**
     * 9. recommendProcess - function
     * - 类似于 rawProcess: 将推荐游戏列表的数据处理成标准状态
     * 
    */
   recommendProcess: function (g) {
        return ({
            category: g.category,
            provider: g.provider,
            code: g.code,
            extra: {
                priority: g.priority,
            }
        });
    },

    /**
     * 10. recommendStrategy - function
     * - 接受一个第一个元素为全局推荐游戏列表、第二个元素为agent推荐游戏列表的array为参数
     * - 返回一个推荐游戏array
     * - strategy将在 recommendProcess之前调用：数据尚未被处理
     * - 默认stategy为 general.concat(operator); 主要使用方式包括 将operator推荐游戏前置 或者将某一类型的推荐游戏前置
     * 
    */
   recommendStrategy: ([general, operator]) => 
        general.filter(g => g.provider == 'PNG').concat(operator)

    /**
     * 11. launchGame - function
     * - 打开游戏请求的处理函数
     * - 在instance上使用getLaunchUrl时触发
     *   resolve and launchGame get success status and data
     *   reject and  launchGame get error status and data
     * 
    **/
    launchGame: (game, { resolve, reject }) => {
        const {provider, code} = game;
        fetch(`/open-game?provider=${provider}&code=${code}`)
        .then(response => {
            if (provider == 'PNG') resolve('....' + response);
            else resolve(response);
        })
        .catch(error => {
            reject(error);
        })
    },

    /**
     * 12. getBalance - function
     * - 获取平台余额的处理函数
     * - 在instance上使用 getBalance 时触发
     *   resolve and getBalance get success status and data
     *   reject and  getBalance get error status and data
     * 
    **/
    getBalance: (provider, { resolve, reject }) => {
        fetch(`/get——balance?provider=${provider}`)
        .then(balance => {
            resolve(balance);
        })
        .catch(error => {
            reject(error);
        })
    },

    /**
     * 12. transform
     * 
     * format: {order: fn, rename:fn}
     * 
     * - transform主要是用于
     * - transform会在gmservice即将输出的时候执行，不会对内容数据产生影响：因此可以配合config使用、即时更新输出数据
     * - 主要的使用方式： 更改名字、改变order，以及在切换语言的时候即时更新所有受影响的部分
     * 
     * - order: function (groups, providers, categories, types):void
     *   会在原有的object上操作 所有需要使用 .sort/.splice 之类的function
     * 
     * - rename: function(keyword, type)
     *   rename如果没有明确返回值 则使用原本的value
     *   rename type分为了三类 provider, category 以及type
    **/

   /*
    const cateOrder = {
                'sports': 1,
                'live': 2,
                'slots': 3,
                'sports': 4
    }
   */

    transform: {
        rename: function(key, type) {
            if (type == TYPES.GAMETYPE) return ({
                        roulette: "百家乐",
                        slots: '老虎机',
                        probability: '概率',
                        tablegame: '桌游',
                        videopoker: '扑克游戏',
                        'video-bingo': "宾果"
                    })[key];
            if (type == TYPES.CATEGORY) return ({
                slots: '电子游戏'
            })[key];
        },
        order: function(groups, providers, categories, types) {
            if (categories) categories.sort((cateA, cateB) => {
                return cateOrder[cateA] - cateOrder[cateB]
            })
        }
    },



})
```


# API 

- __group: provider + category__ 比如说 [PNG slots] [MG slots] [MG live]是三个不同的group

- __active group__ 任何时候都会存在一个provider+category的组合作为active group；默认值为  ```{provider: null, category: null} ```.  gamelist, types, recommend等数据都会受当前active group影响
    - 同时存在provider与category
    - 只有provider - 显示provider下所有category的数据
    - 只有category - 显示category下所有provider的数据
    - 都没有 - 显示全局数据

- __filters__ gameStream emit的是active group中的游戏列表, filterStream emit的是gameStream 的数据经过筛选后的结果. 筛选的filter有三种
    - keyword: 游戏名称
    - type: 游戏类型
    - limitTo: 最大显示游戏数 

- __stream__ stream即rxjs中的observable: 在api中主要操作方式为 .subscribe ; 所有以stream结尾命名的api都会多次emit数据：只需要subscribe一次； 如果只想获取一次数据可以使用 take(1) 比如 ```instance.typeStream.take(1).subscribe(types => {/*....*/})```

|#| API | 功能 | 示例 |
|-|-|-|-|
1 |recommends(groupObject) | 根据provider + category获取推荐游戏列表 | ```instance.recommends({provider: 'PNG', category: 'live'})```<br>```.subscribe(games => {/* .... */})``` |
2 |recommendStream | 当前active group的推荐游戏列表 | ```instance.recommendStream```<br>```.subscribe(games => {/* .... */})``` |
3|list | 完整游戏列表 | ```instance.list(games => {/* do something */})``` |
4|setGroup | 设置active的group | ```instance.setGroup({provider:'PNG', category: 'slots'}})``` <br><br> ```instance.setGroup({provider:'Betsoft'})``` |
5|setFilter(filters, reset) <br> <br> (limitTo默认为20) <br> reset如果为true 则会将filters先reset再加上传入的filters; 如果为false，则只会更新传入的filter，未传入的部分不变 | 更新Filter | ```instance.setFilter({keyword: 'diamond'})```<br>```instance.setFilter({ type:'slot' })```<br>```instance.setFilter({ limitTo: 50})```<br>```instance.setFilter({}, true)``` |
6|loadMore(howmany?) <br> 默认额外加载20个游戏 | 加载更多游戏(更新filter) | ```instance.loadMore()``` <br> ```instance.loadMore(30)``` |
7|gameStream | 当前active group对应游戏列表 | ```instance.gameStream``` <br> ```.subscribe(games => {/* do something */})```|
8|filteredStream | filter之后的当前active group对应游戏列表 | ```instance.filteredStream``` <br> ```.subscribe(games => {/* do something */})``` |
9|gameGroupStream （相同于gameStream+typeStream）| active group的数据 | |
10|typeStream <br> (注意！只有当active group同时设置了provider与category, typeStream才会有数据) | 当前active group对应类型列表 | ```instance.typeStream``` <br> ```.subscribe(types => {/* do something */})``` |
11|activeGroup | 当前active的group | ```instance.activeGroup``` <br> ```.subscribe(group => {/* do something */})``` |
12|getLaunchUrl | 打开游戏 | ```api.getLaunchUrl({ provider: 'PNG', code: 1000 })``` <br> ```.subscribe(data => { data.status == 'success' && window.open(data.data) || alert(data.data) ``` |
13|getBalance | 获取平台余额 | ```api.getBalance('PNG')``` <br> ```.subscribe(data => { data.status == 'success' && alert('balance=' + data.data) || alert(data.data) ``` |
14|providers <br> (会被transform影响)| 平台列表 | ```instance.providers``` <br> ```.subscribe(providers => {/* do something */})``` |
15|balances <br> 会在getBalance请求完成时更新(注意: 进入或退出loading的状态不会造成balances的emit) | 平台余额Map | ```instance.balances``` <br> ```.subscribe(balances => {/* do something */})``` |
16|categories <br> (会被transform影响)| 类型列表 | ```instance.categories``` <br> ```.subscribe(categories => {/* do something */})``` |
17|groups <br> (会被transform影响)| group列表 | ```instance.groups``` <br> ```.subscribe(groups => {/* do something */})``` |
18|indicators <br> 两个subscribable的indicator: moreToLoad (boolean) , loading (array of targets in loading state) | 当前数据状态的指示 | ```indicators.moreToLoad``` <br> ```.subscribe(moreToLoad => moreToLoad && show(loadMoreButton); })```<br><br>  ```indicators.loading``` <br> ```.subscribe(states => { app.ready = states.length === 0 })``` |
19|gallery<br>(会被transform影响)| 按照一定格式获取全部或者部分数据 | ```instance.gallery({}, GALLERY_FORMAT.PROVIDERS_OF_CATEGORIES).subscribe(games => {...})``` <br> ```instance.gallery({provider:'PNG'}).subscribe(games => {...})``` |
20|config | 重新设置rename/order函数; 操作之后各个stream将会重新计算并emit 因此可以支持语言切换 | ```api.config({transform: {rename: fn}})```|
            
                


Loading State 列表
- LOADING_GAMELIST
- LOADING_PROVIDERS
- LOADING_CATEGORIES
- LOADING_RECOMMENDS

GALLERY_FORMAT 列表
- CATEGORIES_OF_PROVIDERS
- PROVIDERS_OF_CATEGORIES

TYPES 列表
- PROVIDER
- GAMETYPE
- CATEGORY

---
_Source: https://npm.io/package/@articulatesydney/gmservice · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
