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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@articulatesydney/gmservice

v2.1.1

Published

GM-SERVICE ====

Readme

GM-SERVICE

About

本JS库目的在于管理游戏列表 主要特性有

  1. 提供常用的异步数据支持
  2. 封装常用的展示游戏列表的方式
  3. 基于rxjs 非常extendable

GMService基于rxjs,数据交互操作全部为异步实现。 获取数据的接口的使用方式基本上都是

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
var instance = new gmservice({
    games: GAMELISR,
    cache: 10000,
    launchGame: function (game, { resolve }) {
        // ... resolve(xxxx)
    }
})
  1. 从instance提供的api中获取数据

获取provider

instance.providers.subscribe(providers => {
    console.log(providers);
})

获取完整游戏列表

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 - 这个设计是为了实现一个游戏可以同时有多个类型的需求
{
    slot: true,
    jackpot: true,
    asian: true
}
  1. extra: object - 用于记录额外的数据:可能在自定义的搜索中使用

配置列表

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