import { EventEmitter2 } from "eventemitter2" ; import { stringifyQuery } from "coral-common/utils" ; import ensureNoEndSlash from "coral-common/utils/ensureNoEndSlash" ; import urls from "coral-framework/helpers/urls" ; import { RefreshAccessTokenCallback } from "./Coral" ; import { Decorator , withAutoHeight , withClickEvent , withConfig , withEventEmitter , withIndexedDBStorage , withIOSSafariWidthWorkaround , withKeypressEvent , withLiveCommentCount , withPostMessageStorage , withRenderTargets , withSetCommentID , } from "./decorators" ; import withRefreshAccessToken from "./decorators/withRefreshAccessToken" ; import { FrameControl , FrameControlFactory } from "./FrameControl" ; import injectCountScriptIfNeeded from "./injectCountScriptIfNeeded" ; import onIntersect , { OnIntersectCancellation } from "./onIntersect" ; import { defaultPymControlFactory } from "./PymFrameControl" ; import { coerceStorage } from "./utils" ; export interface StreamEmbedConfig { storyID ? : string ; storyURL ? : string ; storyMode ? : string ; commentID ? : string ; autoRender ? : boolean ; title : string ; eventEmitter : EventEmitter2 ; id : string ; rootURL : string ; accessToken ? : string ; bodyClassName ? : string ; enableDeprecatedEvents ? : boolean ; customCSSURL ? : string ; refreshAccessToken ? : RefreshAccessTokenCallback ; amp ? : boolean ; } export class StreamEmbed { /**
     * Every interval rounded to this value in ms will be passed when creating the
     * stream embed iframe to ensure that it is loaded fresh.
     */ private readonly requestExpiryInterval = 15 * 60 * 1000 ; /**
     * control provides an interface to the iFrame controller.
     */ private readonly control : FrameControl ; /**
     * eventEmitter provides an interface to events emitted by Coral.
     */ private readonly eventEmitter : EventEmitter2 ; private ready = false ; private clearAutoRender : OnIntersectCancellation | null = null ; constructor ( config , controlFactory = defaultPymControlFactory ) { const element = document . getElementById ( config . id ) ; if ( ! element ) { throw new Error ( `element ${ config . id } was not found` ) ; } this . eventEmitter = config . eventEmitter ; config . eventEmitter . once ( "ready" , ( ) => { this . ready = true ; } ) ; const query = stringifyQuery ( { storyID : config . storyID , storyURL : config . storyURL , storyMode : config . storyMode , commentID : config . commentID , customCSSURL : config . customCSSURL , v : process . env . TALK_VERSION ? process . env . TALK_VERSION : "dev" , ts : Math . round ( Date . now ( ) / this . requestExpiryInterval ) * this . requestExpiryInterval , } ) ; const streamURL = ensureNoEndSlash ( config . rootURL ) + urls . embed . stream ; const url = `${ streamURL }?${ query }` ; const decorators = [ withIOSSafariWidthWorkaround , withAutoHeight ( Boolean ( config . amp ) ) , withClickEvent , withSetCommentID , withEventEmitter ( config . eventEmitter , config . enableDeprecatedEvents ) , withLiveCommentCount ( config . eventEmitter ) , withPostMessageStorage ( coerceStorage ( "localStorage" ) , "localStorage" ) , withPostMessageStorage ( coerceStorage ( "sessionStorage" ) , "sessionStorage" ) , withIndexedDBStorage ( ) , withConfig ( { accessToken : config . accessToken , bodyClassName : config . bodyClassName , version : process . env . TALK_VERSION , amp : Boolean ( config . amp ) , } ) , withKeypressEvent , withRefreshAccessToken ( config . refreshAccessToken ) , withRenderTargets ( url , config . id ) , ] ; this . control = controlFactory ( { id : config . id , title : config . title , decorators , url , } ) ; injectCountScriptIfNeeded ( config . rootURL ) ; if ( config . commentID ) { setTimeout ( ( ) => config . eventEmitter . emit ( "showPermalink" ) , 0 ) ; } if ( config . autoRender ) { if ( config . commentID ) { this . render ( ) ; } else { this . clearAutoRender = onIntersect ( element , ( ) => { this . clearAutoRender = null ; this . render ( ) ; } , { rootMargin : "100px" , threshold : 1.0 , } ) ; } } } public on ( eventName , callback ) { return this . eventEmitter . on ( eventName , callback ) ; } public off ( eventName , callback ) { return this . eventEmitter . off ( eventName , callback ) ; } public login ( token ) { if ( ! this . ready ) { this . eventEmitter . once ( "ready" , ( ) => { this . control . sendMessage ( "login" , token ) ; } ) ; return ; } this . control . sendMessage ( "login" , token ) ; } public logout ( ) { if ( ! this . ready ) { this . eventEmitter . once ( "ready" , ( ) => { this . control . sendMessage ( "logout" ) ; } ) ; return ; } this . control . sendMessage ( "logout" ) ; } public remove ( ) { if ( this . clearAutoRender ) { this . clearAutoRender ( ) ; this . clearAutoRender = null ; return ; } if ( ! this . control . rendered ) { throw new Error ( "instance not mounted" ) ; } this . control . remove ( ) ; } public get rendered ( ) { return this . control . rendered ; } public render ( ) { if ( this . control . rendered ) { return ; } this . control . render ( ) ; } }