module import { consolePrint , CurrentEnvironment , eventPatternMatch , forceType } from './util' ; import Analytics from './analytics' ; import { DebugOptions } from './debug' ; import { Messenger } from './messenger' ; import mxy from './muxy' ; import Observer from './observer' ; import StateClient from './state-client' ; import { PurchaseClient , Transaction , TransactionResponse } from './purchase-client' ; import { ContextUpdateCallbackHandle , HighlightChangedCallbackHandle , Position , TwitchContext } from './twitch' ; import Ext from './twitch-ext' ; import User , { UserUpdateCallbackHandle } from './user' ; /**
 * The response from  getAllState .
 *
 *  typedef AllState property { Object } extension property { Object } channel property { Object } viewer property { Object } extension_viewer export interface AllState { extension : object ; channel : object ; viewer : object ; extension_viewer : object ; } /**
 * The response from  getAccumulateData .
 *
 *  typedef AccumulateData property { number } latest property { AccumulatePayload [ ] } data export interface AccumulateData { latest : number ; data : AccumulatePayload [ ] ; } typedef AccumulatePayload property { number } observed property { string } channel_id property { string } opaque_user_id property { string } user_id property { Object } data export interface AccumulatePayload { observed : number ; channel_id : string ; opaque_user_id : string ; user_id : string ; data : object ; } /**
 * The response from  getVoteData .
 *
 *  typedef VoteData property { number } count property { number } mean property { number [ ] } specific property { number } stddev property { number } sum property { number } vote export interface VoteData { count : number ; mean : number ; specific : number [ ] ; stddev : number ; sum : number ; vote : number ; } /**
 * A single vote cast in a poll, returned from  getFullVoteLogs typedef VoteLogEntry property { string } identifier property { number } value export interface VoteLogEntry { identifier : string ; value : number ; } /**
 * The response from  getFullVoteLogs typedef VoteLog property { object [ ] } result export interface VoteLog { result : VoteLogEntry [ ] ; } /**
 * The response from  getRankData .
 *
 *  typedef RankData property { RankScore [ ] } data export interface RankData { data : RankScore [ ] ; } typedef RankScore property { string } key property { number } score export interface RankScore { key : string ; score : number ; } typedef { Object } RankResponse export interface RankResponse { accepted : boolean ; original : string | undefined ; } /**
 * The response from  getEligibleCodes .
 *
 *  typedef EligibleCodes property { Array < number > } eligible export interface EligibleCodes { eligible : number [ ] ; } /**
 * The response from  getRedeemedCodes .
 *
 *  typedef RedeemedCodes property { Array < Array < string > > } redeemed export interface RedeemedCodes { redeemed : string [ ] [ ] ; } /**
 * The response from  redeemCode .
 *
 *  typedef RedeemResult property { string } code property { Array < string > } all_prizes export interface RedeemResult { code ? : string ; all_prizes : string [ ] ; } /**
 * A single entry in the response from  getExtensionUsers .
 *
 *  typedef ExtensionUser property { string } twitch_id export interface ExtensionUser { twitch_id : string ; } /**
 * The response from  getExtensionUsers .
 *
 *  typedef ExtensionUsersResult property { string } next property { ExtensionUser [ ] } results export interface ExtensionUsersResult { next : string ; results : ExtensionUser [ ] ; } property property property property property property property export interface TriviaQuestion { id : string ; name : string ; short_name : string ; description : string ; image ? : string ; order ? : number ; options ? : TriviaOption [ ] ; state ? : string ; } property property property property property export interface TriviaOption { id : string ; name : string ; short_name : string ; description : string ; image ? : string ; order ? : number ; } property export interface TriviaTeam { id : string ; } export interface TriviaLeaderboard { leaderboard : TriviaLeaderboardTeam [ ] ; } property property property export interface TriviaLeaderboardTeam { team_id : string ; combined_score : number ; questions : TriviaLeaderboardQuestionResultsMap ; } export interface TriviaLeaderboardQuestionResultsMap { [ key ] : TriviaLeaderboardQuestionResults ; } property property property property property property property export interface TriviaLeaderboardQuestionResults { question_id : string ; broadcaster_correct : boolean ; team_participation : number ; team_participants : number ; percent_correct : number ; team_votes : number ; score_values : number [ ] ; } export interface TriviaStateResponse { state : string ; } export interface TriviaQuestionResponse { questions : TriviaQuestion [ ] ; } export enum TriviaQuestionState { Inactive = 'state-inactive' , Unlocked = 'state-unlocked' , Locked = 'state-locked' , Results = 'state-results' } property property property property export interface UserInfo { ip_address : string ; registered : boolean ; server_time : number ; visualization_id : string ; } /**
 * The Muxy Extensions SDK, used to communicate with Muxy's Extension Backend Service.
 *
 * Instances of this class created through the global `Muxy` object can be used to easily
 * interact with Muxy's Extension Backend Service. It includes functionality to aggregate
 * and persist user data, set extension configuration, send analytics events and authenticate
 * broadcasters across servers and applications.
 *
 * To begin using the SDK, create a new instance by calling `const sdk = Muxy.SDK()`.
 *
 * **Note for Overlay App Developers:**
 * An instance of the Muxy SDK is automatically created for you that is namespaced to your
 * app id. You can access it in any app that imports AppMixin as `this.muxy.<method>`. The
 * methods described below behave similarly to how they would in an extension context, however
 * all data is exclusive to your app. Differences are noted in the comments to the individual
 * methods.
 */ export default class SDK { public loadPromise : Promise < void > ; public identifier : string ; public client : StateClient ; public analytics : Analytics ; public messenger : Messenger ; public user : User ; public SKUs : object [ ] ; public timeOffset : number ; public purchaseClient : PurchaseClient ; public debug : DebugOptions ; public userObservers : Observer < User > ; public contextObservers : Observer < TwitchContext > ; public highlightObservers : Observer < boolean > ; ignore constructor ( id ? ) { if ( ! mxy . setupCalled ) { throw new Error ( 'Muxy.setup() must be called before creating a new SDK instance' ) ; } const identifier = id || mxy . twitchClientID ; if ( ! identifier ) { return null ; } if ( ! mxy . watchingAuth ) { mxy . watchingAuth = true ; mxy . watchAuth ( identifier ) ; } if ( ! mxy . SDKClients [ identifier ] ) { this . setup ( identifier , mxy . client , mxy . user , mxy . messenger , mxy . purchaseClient , mxy . analytics , mxy . loadPromise , mxy . SKUs , mxy . debugOptions ) ; mxy . SDKClients [ identifier ] = this ; } return mxy . SDKClients [ identifier ] ; } since return { Promise } example public loaded ( ) { return this . loadPromise ; } since example public updateUser ( user ) { this . user = user ; this . userObservers . notify ( user ) ; } /**
     * Registers a new callback for when the current user's info is updated.
     */ public onUserUpdate ( callback ) { const handler = new UserUpdateCallbackHandle ( callback ) ; this . userObservers . register ( handler ) ; return handler ; } /**
     * Registers a new callback for when the context is updated.
     */ public onContextUpdate ( callback ) { const handler = new ContextUpdateCallbackHandle ( callback ) ; this . contextObservers . register ( handler ) ; return handler ; } /**
     * Registers a new callback for when the highlight is updated.
     */ public onHighlightChanged ( callback ) { const handler = new HighlightChangedCallbackHandle ( callback ) ; this . highlightObservers . register ( handler ) ; return handler ; } return { Date } public getOffsetDate ( ) { return new Date ( new Date ( ) . getTime ( ) + this . timeOffset ) ; } /**
     * Returns a promise to get the user object. This automatically
     * waits for .loaded() to resolve.
     */ public getUser ( ) { return this . loaded ( ) . then ( ( ) => { return Promise . resolve ( this . user ) ; } ) ; } /**
     * Invokes a request to the backend.
     */ public signedRequest < DataType = unknown , ResponseType = unknown > ( method , endpoint , data ) { return this . client . signedRequest < DataType , ResponseType > ( this . identifier , method , endpoint , data ) ; } /**
     * Data Accumulation
     */ async since throws param { string } accumulationID param { number } start return { Promise < AccumulateData > } example public getAccumulateData ( accumulationID , start ) { forceType ( accumulationID , 'string' ) ; return this . client . getAccumulation ( this . identifier , accumulationID , start ) ; } deprecated public getAccumulation ( accumulationID , start ) { return this . getAccumulateData ( accumulationID , start ) ; } since param { string } accumulationID param { DataType } data return { Promise < ResponseType > } example public accumulate < DataType = unknown , ResponseType = unknown > ( accumulationID , data ) { forceType ( accumulationID , 'string' ) ; return this . client . accumulate < DataType , ResponseType > ( this . identifier , accumulationID , data ) ; } /**
     * User Voting
     */ async since throws param { string } voteID return { Promise < VoteData > } example public getVoteData ( voteID ) { forceType ( voteID , 'string' ) ; return this . client . getVotes ( this . identifier , voteID ) ; } async param voteID return { Promise < VoteLog > } example public getFullVoteLogs ( voteID ) { forceType ( voteID , 'string' ) ; return this . client . getFullVoteLogs ( this . identifier , voteID ) ; } async since throws param { string } voteID param { number } value return { Promise } example public vote ( voteID , value ) { forceType ( voteID , 'string' ) ; forceType ( value , 'number' ) ; return this . client . vote ( this . identifier , voteID , { value } ) ; } /**
     * User Ranking
     */ async since throws param { string } rankID return { Promise < RankData > } example public getRankData ( rankID ) { forceType ( rankID , 'string' ) ; return new Promise ( ( accept , reject ) => { this . client . getRank ( this . identifier , rankID ) . then ( ( data ) => { accept ( data ) ; } ) . catch ( reject ) ; } ) ; } async since throws param { string } rankID param { string } value return { Promise < RankResponse > } example public rank ( rankID , value ) { forceType ( rankID , 'string' ) ; forceType ( value , 'string' ) ; return this . client . rank ( this . identifier , rankID , { key : value } ) ; } async since throws param { string } rankID return { Promise < ResponseType > } public clearRankData ( rankID ) { forceType ( rankID , 'string' ) ; return this . client . deleteRank < ResponseType > ( this . identifier , rankID ) ; } deprecated public getRankingData ( rankID ) { return this . getRankData ( rankID ) ; } deprecated public clearRanking ( rankID ) { return this . clearRanking ( rankID ) ; } /**
     * Server Config
     */ /**
     * Sets the server channel config to a JS object. Future calls to  getConfig or  getChannelConfig by **any**
     * user on this channel will have a clone of this object in the `channel` field.
     *
     * Broadcaster-only functionality.
     *
     *  async since param { DataType } config return { Promise < ResponseType > } example public setChannelConfig < DataType = unknown , ResponseType = unknown > ( config ) { return this . client . setChannelConfig < DataType , ResponseType > ( this . identifier , config ) ; } /**
     * Sets the server extension config to a JS object. Future calls to  getConfig or  getExtensionConfig by **any**
     * user on this channel will have a clone of this object in the `extension` field.
     *
     * Broadcaster-only functionality.
     *
     *  async since param { DataType } config return { Promise < ResponseType > } example public setExtensionConfig < DataType = unknown , ResponseType = unknown > ( config ) { return this . client . setExtensionConfig < DataType , ResponseType > ( this . identifier , config ) ; } async return { Promise < ResponseType > } public getConfig < ResponseType = unknown > ( ) { return this . client . getConfig < ResponseType > ( this . identifier ) ; } async return { Promise < ResponseType > } public getChannelConfig < ResponseType = unknown > ( ) { return this . client . getChannelConfig < ResponseType > ( this . identifier ) ; } async return { Promise < ResponseType > } public getExtensionConfig < ResponseType = unknown > ( ) { return this . client . getExtensionConfig < ResponseType > ( this . identifier ) ; } /**
     * User State
     */ /**
     * Sets the channel specific viewer-specific state to a JS object, this can be called by
     * any viewer.
     * Future calls to  getAllState by **this** user will have a clone of this object in the
     * `viewer` field.
     *  async since param { DataType } state return { Promise < ResponseType > } example public setViewerState < DataType = unknown , ResponseType = unknown > ( state ) { return this . client . setViewerState < DataType , ResponseType > ( this . identifier , state ) ; } /**
     * Sets the extension wide viewer-specific state to a JS object, this is only a valid call for a
     * user that has shared their identity.
     * Future calls to  getAllState by **this** user will have a clone of this object in the
     * `extension_viewer` field.
     *  async since param { DataType } state return { Promise < ResponseType > } example public setExtensionViewerState < DataType = unknown , ResponseType = unknown > ( state ) { return this . client . setExtensionViewerState < DataType , ResponseType > ( this . identifier , state ) ; } async param userStates return { Promise } example public patchExtensionViewerState < DataType = unknown , ResponseType = unknown > ( userStates ) { return this . client . patchExtensionViewerState < DataType , ResponseType > ( this . identifier , userStates ) ; } /**
     * Sets the extension wide state to a JS object, this may only be called in a broadcaster context
     * for the extension owner. Extension owner may be configured through the development portal.
     * Future calls to  getAllState by all users will have a clone of this object in the
     * `extension` field.
     *  async since param { DataType } state return { Promise < ResponseType > } example public setExtensionState < DataType = unknown , ResponseType = unknown > ( state ) { return this . client . setExtensionState < DataType , ResponseType > ( this . identifier , state ) ; } async since param { DataType } state return { Promise < ResponseType > } example public setExtensionSecretState < DataType = unknown , ResponseType = unknown > ( state ) { return this . client . setExtensionSecretState < DataType , ResponseType > ( this . identifier , state ) ; } /**
     * Sets the channel-specific state to a JS object. Future calls to  getAllState by **any**
     * user on this channel will have a clone of this object in the `channel` field.
     *
     * Broadcaster-only functionality.
     *
     *  async since param { DataType } state return { Promise < ResponseType > } example public setChannelState < DataType = unknown , ResponseType = unknown > ( state ) { return this . client . setChannelState < DataType , ResponseType > ( this . identifier , state ) ; } async since return { Promise < AllState > } example public getAllState ( ) { return this . client . getState ( this . identifier ) ; } async return { Promise < ResponseType > } public getExtensionState < ResponseType = unknown > ( ) { return this . client . getExtensionState ( this . identifier ) ; } async return { Promise < ResponseType > } public getChannelState < ResponseType = unknown > ( ) { return this . client . getChannelState < ResponseType > ( this . identifier ) ; } async return { Promise < ResponseType > } public getExtensionViewerState < ResponseType = unknown > ( ) { return this . client . getExtensionViewerState < ResponseType > ( this . identifier ) ; } async return { Promise < ResponseType > } public getViewerState < ResponseType = unknown > ( ) { return this . client . getViewerState < ResponseType > ( this . identifier ) ; } async return { Promise < ResponseType > } public getExtensionSecretState < ResponseType = unknown > ( ) { return this . client . getExtensionSecretState < ResponseType > ( this . identifier ) ; } async param users return { Promise < ResponseType > } example public multiGetExtensionViewerState < ResponseType = unknown > ( users ) { if ( users . length > 1000 ) { throw new Error ( 'Too many users specified in call to multiGetExtensionViewerState' ) ; } return this . client . multiGetExtensionViewerState < ResponseType > ( this . identifier , users ) ; } /**
     * JSON Store
     */ /**
     * The JSON store is used similarly to the channel state, in that a broadcaster can use it to
     * store arbitrary JSON data that is accessible to all viewers. The stored data is specific to
     * a particular channel and cannot be accessed by viewers of a different channel.
     *
     * Unlike channel state however, each channel can have several JSON stores, accessed by different
     * keys. The data associated with each key must be under 2KB, but there is no limit to the number
     * of keys in use.
     *
     * Also, when pushing new data to the JSON store, a messenger event is automatically sent to all
     * active viewers of the associated channel and the broadcaster's live and config pages. This
     * event will have the format `json_store_update:${key}`. See  listen for details on
     * receiving this event.
     *
     *  async since throws param { string ? } key return { Promise < ResponseType > } example public getJSONStore < ResponseType = unknown > ( key ? ) { if ( key ) { forceType ( key , 'string' ) ; } return this . client . getJSONStore < ResponseType > ( this . identifier , key ) ; } /**
     * Two-Factor Auth
     */ async since throws param { string } pin return { Promise } example public validateCode ( pin ) { forceType ( pin , 'string' ) ; return this . client . validateCode ( this . identifier , pin ) ; } async since return { Promise < ResponseType > } property example public pinTokenExists < ResponseType = unknown > ( ) { return this . client . pinTokenExists < ResponseType > ( this . identifier ) ; } async since return { Promise } example public revokeAllPINCodes ( ) { return this . client . revokeAllPINCodes ( this . identifier ) ; } /**
     * Event System
     */ /**
     * Sends a message to all listening clients. And viewers or broadcasters listening for the
     * event name will be automatically notified. See  listen for receiving events.
     *
     * Broadcaster-only functionality.
     *
     *  async since param { string } event param { string | * } userID param { * } data example public send ( event , userID , data ? ) { if ( ! mxy . didLoad ) { throw new Error ( 'sdk.loaded() was not complete. Please call this method only after the promise has resolved.' ) ; } forceType ( event , 'string' ) ; let target = 'broadcast' ; let realData = data ; if ( ! data ) { realData = userID ; } else { target = `whisper-${ userID }` ; } this . messenger . send ( this . identifier , event , target , realData , this . client ) ; } /**
     * Registers a callback to listen for events. In general, events are named in the form
     * `event[:identifier]`, where the identifier is the `event` parameter to  send .
     *
     * You can listen to wildcards by using * instead of an event or identifier name.
     *
     * Some methods also automatically send special namespaced events. See  vote and
     *  getJSONStore for examples.
     *
     * You can listen for these events by using `vote_update:next_game` or `vote_update:*`
     * to receive vote updates for specifically the `next_game` vote id, or all vote
     * updates respectively.
     *
     *  since param { string } inEvent param { string | Function } inUserID param { Function } inCallback - A callback with the signature `function(body, eventName)`.
     * This callback will receive the message body as its first parameter and the `event` parameter
     * to  send as the second.
     *
     *  return { Object } example public listen ( inEvent , inUserID , inCallback ? ) { if ( ! mxy . didLoad ) { throw new Error ( 'sdk.loaded() was not complete. Please call this method only after the promise has resolved.' ) ; } const realEvent = `${ CurrentEnvironment ( ) . environment }:${ this . identifier }:${ inEvent }` ; let l = 'broadcast' ; let callback = inCallback ; if ( callback ) { l = `whisper-${ inUserID }` ; } else { callback = inUserID ; } let messageBuffer = [ ] ; const cb = ( msg ) => { try { if ( CurrentEnvironment ( ) . environment === 'production' ) { if ( eventPatternMatch ( msg . event , `${ this . identifier }:${ inEvent }` ) ) { const truncatedEvent = msg . event . split ( ':' ) . slice ( 1 ) . join ( ':' ) ; callback ( msg . data , truncatedEvent ) ; return ; } } if ( eventPatternMatch ( msg . event , realEvent ) ) { const truncatedEvent = msg . event . split ( ':' ) . slice ( 2 ) . join ( ':' ) ; const serialized = JSON . stringify ( msg ) ; const now = new Date ( ) . valueOf ( ) ; let deduped = false ; messageBuffer . forEach ( ( b ) => { if ( b . content === serialized ) { if ( now - b . timestamp < 5 * 1000 ) { deduped = true ; } } } ) ; if ( deduped ) { return ; } messageBuffer . unshift ( { content : serialized , timestamp : now } ) ; messageBuffer = messageBuffer . slice ( 0 , 10 ) ; callback ( msg . data , truncatedEvent ) ; } } catch ( err ) { consolePrint ( err , { type : 'error' } ) ; } } ; return this . messenger . listen ( this . identifier , l , cb ) ; } since param { Object } handle public unlisten ( handle ) { return this . messenger . unlisten ( this . identifier , handle ) ; } /**
     * Transaction System
     */ async since throws return { Promise < [ ] Product>} Resolves with an array of  Product * objects for each available sku.
     *
     *  example public getProducts ( ) { if ( ! mxy . didLoad ) { throw new Error ( 'sdk.loaded() was not complete. Please call this method only after the promise has resolved.' ) ; } return this . purchaseClient . getProducts ( ) ; } async since throws param { string } sku example public purchase ( sku ) { if ( ! mxy . didLoad ) { throw new Error ( 'sdk.loaded() was not complete. Please call this method only after the promise has resolved.' ) ; } forceType ( sku , 'string' ) ; this . purchaseClient . purchase ( sku ) ; } since throws param { function } callback example public onUserPurchase ( callback ) { if ( ! mxy . didLoad ) { throw new Error ( 'sdk.loaded() was not complete. Please call this method only after the promise has resolved.' ) ; } forceType ( callback , 'function' ) ; this . purchaseClient . onUserPurchase ( callback ) ; } since throws param { function } callback example public onUserPurchaseCanceled ( callback ) { if ( ! mxy . didLoad ) { throw new Error ( 'sdk.loaded() was not complete. Please call this method only after the promise has resolved.' ) ; } forceType ( callback , 'function' ) ; this . purchaseClient . onUserPurchaseCanceled ( callback ) ; } /**
     * Analytics
     */ async since param { string } name param { number } value param { string } label public sendAnalyticsEvent ( name , value = 1 , label = '' ) { this . analytics . sendEvent ( this . identifier , name , value , label ) ; } /**
     * Monetization
     */ param { string } sku public beginPurchase ( sku ) { if ( this . SKUs . length === 0 ) { throw new Error ( 'beginPurchase() cannot be used unless SKUs are provided.' ) ; } forceType ( sku , 'string' ) ; return Ext . beginPurchase ( sku ) ; } async return { Object } public getPrices ( ) { if ( this . SKUs . length === 0 ) { throw new Error ( 'getPrices() cannot be used unless SKUs are provided.' ) ; } return new Promise ( ( resolve ) => { Ext . getPrices ( ( prices ) => { resolve ( prices ) ; } ) ; } ) ; } param { Function } callback public onReloadEntitlements ( callback ) { if ( this . SKUs . length === 0 ) { throw new Error ( 'onReloadEntitlements() cannot be used unless SKUs are provided.' ) ; } return Ext . onReloadEntitlements ( callback ) ; } param { function } callback public onVisibilityChanged ( callback ) { return Ext . onVisibilityChanged ( callback ) ; } param { function } callback public onPositionChanged ( callback ) { return Ext . onPositionChanged ( callback ) ; } async throws param { number } prize_idx return { Promise < RedeemResult > } public redeemCode ( prizeIndex ) { forceType ( prizeIndex , 'number' ) ; return this . client . redeemCode ( this . identifier , prizeIndex ) ; } async return { Promise < RedeemedCodes > } public getRedeemedCodes ( ) { return this . client . getRedeemedCodes ( this . identifier ) ; } async return { Promise < EligibleCodes > } public getEligibleCodes ( ) { return this . client . getEligibleCodes ( this . identifier ) ; } async return { Promise < any > } public joinExtensionTriviaTeam ( ) { return this . client . joinExtensionTriviaTeam ( this . identifier ) ; } async return { Promise < TriviaTeam > } public getExtensionTriviaJoinedTeam ( ) { return this . client . getExtensionTriviaJoinedTeam ( this . identifier ) ; } async return { Promise < Record < string , unknown > > } public addExtensionTriviaQuestion ( question ) { return this . client . addExtensionTriviaQuestion ( this . identifier , question ) ; } async return { Promise < Record < string , unknown > > } public removeExtensionTriviaQuestion ( triviaQuestionID ) { return this . client . removeExtensionTriviaQuestion ( this . identifier , triviaQuestionID ) ; } async return { Promise < any > } public addExtensionTriviaOptionToQuestion ( questionID , option ) { return this . client . addExtensionTriviaOptionToQuestion ( this . identifier , questionID , option ) ; } async return { Promise < TriviaQuestion > } public removeExtensionTriviaOptionFromQuestion ( questionID , optionID ) { return this . client . removeExtensionTriviaOptionFromQuestion ( this . identifier , questionID , optionID ) ; } async return { Promise < any > } public setExtensionTriviaQuestionState ( questionID , state , winner ? ) { return this . client . setExtensionTriviaQuestionState ( this . identifier , questionID , state , winner ) ; } async return { Promise < Record < string , unknown > > } public setExtensionTriviaQuestionVote ( questionID , optionID ) { return this . client . setExtensionTriviaQuestionVote ( this . identifier , questionID , optionID ) ; } async return { Promise < TriviaQuestionResponse > } public getExtensionTriviaQuestions ( ) { return this . client . getExtensionTriviaQuestions ( this . identifier ) ; } async return { Promise < TriviaQuestion > } public getExtensionTriviaQuestion ( questionID ) { return this . client . getExtensionTriviaQuestion ( this . identifier , questionID ) ; } async return { Promise < TriviaLeaderboardTeam [ ] > } public getExtensionTriviaLeaderboard ( ) { return this . client . getExtensionTriviaLeaderboard ( this . identifier ) ; } /**
     * Admin-level functionality
     */ async return { Promise < ExtensionUsersResult > } public getExtensionUsers ( next ? ) { return this . client . getExtensionUsers ( this . identifier , next ) ; } /**
     * Private Instance Methods
     */ ignore private setup ( identifier , client , user , messenger , purchaseClient , analytics , loadPromise , SKUs , debug ) { ignore this . userObservers = new Observer < User > ( ) ; ignore this . contextObservers = new Observer < TwitchContext > ( ) ; ignore this . loadPromise = loadPromise ; public type { string } this . identifier = identifier ; private type { Client } this . client = client ; private type { Messenger } this . messenger = messenger ; private type { PurchaseClient } this . purchaseClient = purchaseClient ; private type { Analytics } this . analytics = analytics ; public type { User } this . user = user ; public type { Object } this . SKUs = SKUs ; ignore this . debug = debug ; } }