import { binder } from "rx-binder" ; import { kernels } from "rx-jupyter" ; import { of } from "rxjs" ; import { catchError , filter , map , tap } from "rxjs/operators" ; import { ServerConfig as BaseServerConfig } from "@nteract/types" ; type BinderKey = string ; interface BinderOptions { repo : string ; ref ? : string ; binderURL ? : string ; } export interface ServerConfig extends BaseServerConfig { crossDomain : true ; } export const PROVISIONING = "PROVISIONING" ; interface IsItUpHost { type : "PROVISIONING" ; } export const UP = "HOST_UP" ; interface UpHost { type : "HOST_UP" ; config : ServerConfig ; } export const NOHOST = "NOHOST" ; interface NoHost { type : "NOHOST" ; } function makeHost ( { endpoint , token } ) { return { type : UP , config : { crossDomain : true , endpoint , token } } ; } type HostRecord = UpHost | IsItUpHost | NoHost ; const prefix = "@BinderKey@" ; const mybinderURL = "https://mybinder.org" ; function sleep ( duration ) { return new Promise ( resolve => setTimeout ( resolve , duration ) ) ; } export class LocalHostStorage { constructor ( ) { this . handleStorageEvent = this . handleStorageEvent . bind ( this ) ; window . addEventListener ( "storage" , this . handleStorageEvent ) ; } close ( ) { window . removeEventListener ( "storage" , this . handleStorageEvent ) ; } handleStorageEvent ( event ) { const { key , newValue } = event ; if ( typeof key !== "string" ) { return ; } if ( key . startsWith ( prefix ) ) { console . warn ( "Handling storage updates is not implemented. It would be super fantastic to let subscribers know about changes." ) ; const binderOpts = JSON . parse ( key . slice ( prefix . length ) ) ; console . log ( binderOpts ) ; console . log ( newValue ) ; } } createKey ( { repo = "jupyter/notebook" , ref = "master" , binderURL = mybinderURL } ) { return `${ prefix }${ JSON . stringify ( { repo , ref , binderURL } ) }` ; } async checkUp ( host ) { if ( host . type !== UP ) { return false ; } return kernels . list ( host . config ) . pipe ( map ( xhr => { console . log ( xhr ) ; return true ; } ) , catchError ( err => { console . error ( "error listing kernels on server" , err ) ; return of ( false ) ; } ) ) . toPromise ( ) ; } async allocate ( binderOpts ) { let original = this . get ( binderOpts ) ; switch ( original . type ) { case UP : const isUp = await this . checkUp ( original ) ; if ( isUp ) { return original . config ; } break ; case NOHOST : break ; case PROVISIONING : while ( original . type !== UP ) { await sleep ( 1000 ) ; original = this . get ( binderOpts ) ; } if ( original && original . type === UP ) { return original . config ; } } const host = await binder ( binderOpts ) . pipe ( tap ( x => { console . log ( "%c BINDER MESSAGE" , "color: rgb(87, 154, 202)" ) ; console . log ( x ) ; } ) , filter ( msg => msg . phase === "ready" ) , map ( msg => makeHost ( { endpoint : msg . url , token : msg . token } ) ) ) . toPromise ( ) ; if ( ! host . config || ! host . config . endpoint || ! host . config . token || ! host . config . crossDomain ) { throw new Error ( `Bad host created: ${ JSON . stringify ( host , null , 2 ) }` ) ; } this . set ( binderOpts , host ) ; return host . config ; } get ( opts ) { const default_ = { type : "NOHOST" } ; const key = this . createKey ( opts ) ; return JSON . parse ( localStorage . getItem ( key ) || JSON . stringify ( default_ ) ) ; } set ( opts , host ) { const key = this . createKey ( opts ) ; localStorage . setItem ( key , JSON . stringify ( host ) ) ; } }