module import Pusher from 'pusher-js' ; import pako from 'pako' ; import { DebugOptions } from './debug' ; import { CurrentEnvironment , Environment , ENVIRONMENTS } from './util' ; import Config from './config' ; export enum MessengerType { Pusher , Twitch , Server , Unknown } export class CallbackHandle { public target : string ; public cb : ( t : any , datatype : string , message : string ) => void ; } export interface Messenger { channelID : string ; extensionID : string ; send ( id , event , target , body , client ) : void ; listen ( id , topic , callback ) : CallbackHandle ; unlisten ( id , CallbackHandle ) : void ; close ( ) : void ; } function parseMessage ( messageBuffer , id , topic , msg ) { if ( msg . length === 0 ) { return { } ; } if ( msg [ 0 ] == '{' || msg [ 0 ] == '[' ) { return JSON . parse ( msg ) ; } else if ( msg [ 0 ] == '<' ) { const close = msg . indexOf ( '>' ) ; const header = msg . substr ( 1 , close - 1 ) ; const parts = header . split ( ',' ) . map ( function ( x ) { return parseInt ( x , 10 ) ; } ) ; if ( parts . length != 2 ) { return { } ; } const index = parts [ 0 ] ; const count = parts [ 1 ] ; const fragmentLookupKey = id + ':' + topic ; let fragments = messageBuffer [ fragmentLookupKey ] ; if ( ! fragments ) { fragments = [ ] ; for ( let i = 0 ; i < count ; ++ i ) { fragments . push ( '' ) ; } } fragments [ index ] = msg . substr ( close + 1 ) ; let allFragmentsReceived = true ; for ( let i = 0 ; i < count ; ++ i ) { if ( fragments [ i ] . length == 0 ) { allFragmentsReceived = false ; break ; } } messageBuffer [ fragmentLookupKey ] = fragments ; if ( ! allFragmentsReceived ) { return null ; } delete messageBuffer [ fragmentLookupKey ] ; const fullMessage = fragments . join ( '' ) ; const decoded = atob ( fullMessage ) ; const integers = decoded . split ( '' ) . map ( function ( x ) { return x . charCodeAt ( 0 ) ; } ) ; const bytes = new Uint8Array ( integers ) ; return JSON . parse ( pako . inflate ( bytes , { to : 'string' } ) ) ; } else { const decoded = atob ( msg ) ; const integers = decoded . split ( '' ) . map ( function ( x ) { return x . charCodeAt ( 0 ) ; } ) ; const bytes = new Uint8Array ( integers ) ; return JSON . parse ( pako . inflate ( bytes , { to : 'string' } ) ) ; } } class TwitchMessenger implements Messenger { public channelID : string ; public extensionID : string ; public debug : DebugOptions ; constructor ( debug ) { this . channelID = '' ; this . extensionID = '' ; this . debug = debug ; } param id param event param either param body param client public send ( id , event , target , body ) { const data = body || { } ; this . debug . onPubsubSend ( id , event , target , body ) ; window . Twitch . ext . send ( target , 'application/json' , { data , event : `${ CurrentEnvironment ( ) . environment }:${ id }:${ event }` } ) ; } param id param topic param callback return public listen ( id , topic , callback ) { const messageBuffer = { } ; const cb = ( t , datatype , message ) => { try { const parsed = parseMessage ( messageBuffer , id , topic , message ) ; if ( parsed == null ) { return ; } this . debug . onPubsubReceive ( id , topic , parsed ) ; callback ( parsed ) ; } catch ( err ) { } } ; this . debug . onPubsubListen ( id , topic ) ; window . Twitch . ext . listen ( topic , cb ) ; if ( topic === 'broadcast' ) { window . Twitch . ext . listen ( 'global' , cb ) ; } return { cb , target : topic } ; } param id param h public unlisten ( id , h ) { window . Twitch . ext . unlisten ( h . target , h . cb ) ; } public close ( ) { } } class PusherMessenger implements Messenger { public channelID : string ; public extensionID : string ; public debug : DebugOptions ; public client : Pusher . Pusher ; public channel : Pusher . Channel ; public globalChannel : Pusher . Channel ; constructor ( debug ) { this . client = new Pusher ( '18c26c0d1c7fafb78ba2' , { cluster : 'us2' } ) ; this . channelID = '' ; this . debug = debug ; } public send ( id , event , target , body , client ) { const scopedEvent = `${ CurrentEnvironment ( ) . environment }:${ id }:${ event }` ; this . debug . onPubsubSend ( id , event , target , body ) ; client . signedRequest ( id , 'POST' , 'pusher_broadcast' , JSON . stringify ( { data : body , event : scopedEvent , target , user_id : this . channelID } ) ) ; } public listen ( id , topic , callback ) { if ( ! this . channel ) { const channelName = `twitch.pubsub.${ this . extensionID }.${ this . channelID }` ; const globalName = `twitch.pubsub.${ this . extensionID }.all` ; this . channel = this . client . subscribe ( channelName ) ; this . globalChannel = this . client . subscribe ( globalName ) ; } const messageBuffer = { } ; const cb = message => { try { const parsed = parseMessage ( messageBuffer , id , topic , message . message ) ; if ( parsed == null ) { return ; } this . debug . onPubsubReceive ( id , topic , parsed ) ; callback ( parsed ) ; } catch ( err ) { } } ; this . debug . onPubsubListen ( id , topic ) ; this . channel . bind ( topic , cb ) ; this . globalChannel . bind ( topic , cb ) ; return { cb , target : topic } ; } public unlisten ( id , h ) { this . channel . unbind ( h . target , h . cb ) ; this . globalChannel . unbind ( h . target , h . cb ) ; } public close ( ) { this . client . disconnect ( ) ; } } class ServerMessenger implements Messenger { public channelID : string ; public extensionID : string ; public debug : DebugOptions ; constructor ( debug , ch ? ) { this . channelID = ch ; this . debug = debug ; } public send ( id , event , target , body , client ) { this . debug . onPubsubSend ( id , event , target , body ) ; client . signedRequest ( id , 'POST' , 'broadcast' , JSON . stringify ( { data : body , event , target , user_id : this . channelID } ) ) ; } public listen ( id , topic , callback ) { console . error ( 'Server-side message receiving is not implemented.' ) ; return { cb : ( ) => { } , target : '' } ; } public unlisten ( id , handle ) { console . error ( 'Server-side message receiving is not implemented.' ) ; } public close ( ) { } } export default function DefaultMessenger ( debug ) { const type = Config . DefaultMessengerType ( CurrentEnvironment ( ) ) ; switch ( type ) { case MessengerType . Pusher : return new PusherMessenger ( debug ) ; case MessengerType . Twitch : return new TwitchMessenger ( debug ) ; case MessengerType . Server : return new ServerMessenger ( debug ) ; default : throw new Error ( 'Could not determine proper messenger type for environment.' ) ; } }