namespace pxt . Cloud { import Util = pxtc . Util ; export let apiRoot = ( pxt . BrowserUtils . isLocalHost ( ) || Util . isNodeJS ) ? "https://www.makecode.com/api/" : "/api/" ; export let accessToken = "" ; export let localToken = "" ; let _isOnline = true ; export let onOffline = ( ) => { } ; function offlineError ( url ) { let e = new Error ( Util . lf ( "Cannot access {0} while offline" , url ) ) ; e . isOffline = true ; return U . delay ( 1000 ) . then ( ( ) => Promise . reject ( e ) ) ; } export function hasAccessToken ( ) { return ! ! accessToken ; } export function localRequestAsync ( path , data ? ) { return U . requestAsync ( { url : "/api/" + path , headers : { "Authorization" : Cloud . localToken } , method : data ? "POST" : "GET" , data : data || undefined , allowHttpErrors : true } ) ; } export function useCdnApi ( ) { return pxt . webConfig && ! pxt . webConfig . isStatic && ! BrowserUtils . isLocalHost ( ) && ! ! pxt . webConfig . cdnUrl ; } export function cdnApiUrl ( url ) { url = url . replace ( /^\// , '' ) ; if ( ! useCdnApi ( ) ) return apiRoot + url ; const d = new Date ( ) ; const timestamp = d . getUTCFullYear ( ) + ( "0" + ( d . getUTCMonth ( ) + 1 ) ) . slice ( - 2 ) + ( "0" + d . getUTCDate ( ) ) . slice ( - 2 ) ; if ( url . indexOf ( "?" ) < 0 ) url += "?" ; else url += "&" ; url += "cdn=" + timestamp ; return pxt . webConfig . cdnUrl + "/api/" + url ; } export function apiRequestWithCdnAsync ( options ) { if ( ! useCdnApi ( ) ) return privateRequestAsync ( options ) ; options . url = cdnApiUrl ( options . url ) ; return Util . requestAsync ( options ) . catch ( e => handleNetworkError ( options , e ) ) ; } function handleNetworkError ( options , e ) { if ( e . statusCode == 0 ) { if ( _isOnline ) { _isOnline = false ; onOffline ( ) ; } return offlineError ( options . url ) ; } else { return Promise . reject ( e ) ; } } export function privateRequestAsync ( options ) { options . url = pxt . webConfig ?. isStatic && ! options . forceLiveEndpoint ? pxt . webConfig . relprefix + options . url : apiRoot + options . url ; options . allowGzipPost = true ; if ( ! Cloud . isOnline ( ) && ! pxt . BrowserUtils . isPxtElectron ( ) ) { return offlineError ( options . url ) ; } if ( ! options . headers ) options . headers = { } ; if ( pxt . BrowserUtils . isLocalHost ( ) ) { if ( Cloud . localToken ) options . headers [ "Authorization" ] = Cloud . localToken ; } else if ( accessToken ) { options . headers [ "x-td-access-token" ] = accessToken ; } return Util . requestAsync ( options ) . catch ( e => handleNetworkError ( options , e ) ) ; } export function privateGetTextAsync ( path , headers ? ) { return privateRequestAsync ( { url : path , headers } ) . then ( resp => resp . text ) ; } export function privateGetAsync ( path , forceLiveEndpoint = false ) { return privateRequestAsync ( { url : path , forceLiveEndpoint } ) . then ( resp => resp . json ) ; } export function downloadTargetConfigAsync ( ) { if ( ! Cloud . isOnline ( ) ) return Promise . resolve ( undefined ) ; const targetVersion = pxt . appTarget . versions && pxt . appTarget . versions . target ; const url = pxt . webConfig && pxt . webConfig . isStatic ? `targetconfig.json` : `config/${ pxt . appTarget . id }/targetconfig${ targetVersion ? `/v${ targetVersion }` : '' }` ; if ( pxt . BrowserUtils . isLocalHost ( ) ) return localRequestAsync ( url ) . then ( r => r ? r . json : undefined ) ; else return apiRequestWithCdnAsync ( { url } ) . then ( r => r . json ) ; } export function downloadScriptFilesAsync ( id ) { return privateRequestAsync ( { url : id + "/text" , forceLiveEndpoint : true } ) . then ( resp => { return JSON . parse ( resp . text ) ; } ) ; } export async function markdownAsync ( docid , locale ? ) { const MARKDOWN_EXPIRATION = pxt . BrowserUtils . isLocalHostDev ( ) ? 0 : 1 * 60 * 60 * 1000 ; const FORCE_MARKDOWN_UPDATE = MARKDOWN_EXPIRATION * 24 * 7 ; locale = locale || pxt . Util . userLanguage ( ) ; const branch = "" ; const db = await pxt . BrowserUtils . translationDbAsync ( ) ; const entry = await db . getAsync ( locale , docid , branch ) ; const downloadAndSetMarkdownAsync = async ( ) => { try { const r = await downloadMarkdownAsync ( docid , locale , entry ?. etag ) ; if ( ! entry || ( r . md && entry . md !== r . md ) ) { await db . setAsync ( locale , docid , branch , r . etag , undefined , r . md ) ; return r . md ; } return entry . md ; } catch { return "" ; } } ; if ( entry ) { const timeDiff = Date . now ( ) - entry . time ; const shouldFetchInBackground = timeDiff > MARKDOWN_EXPIRATION ; const shouldWaitForNewContent = timeDiff > FORCE_MARKDOWN_UPDATE ; if ( ! shouldWaitForNewContent ) { if ( shouldFetchInBackground ) { pxt . tickEvent ( "markdown.update.background" ) ; downloadAndSetMarkdownAsync ( ) ; } if ( entry . md ) { return entry . md ; } } else { pxt . tickEvent ( "markdown.update.wait" ) ; } } return downloadAndSetMarkdownAsync ( ) ; } function downloadMarkdownAsync ( docid , locale ? , etag ? ) { const packaged = pxt . webConfig ?. isStatic ; const targetVersion = pxt . appTarget . versions && pxt . appTarget . versions . target || '?' ; let url ; if ( packaged ) { url = docid ; const isUnderDocs = /\/?docs\// . test ( url ) ; const hasExt = /\.\w+$/ . test ( url ) ; if ( ! isUnderDocs ) { const hasLeadingSlash = url [ 0 ] === "/" ; url = `docs${ hasLeadingSlash ? "" : "/" }${ url }` ; } if ( ! hasExt ) { url = `${ url }.md` ; } } else { url = `md/${ pxt . appTarget . id }/${ docid . replace ( /^\// , "" ) }?targetVersion=${ encodeURIComponent ( targetVersion ) }` ; } if ( locale != "en" ) { url += `${ packaged ? "?" : "&" }lang=${ encodeURIComponent ( locale ) }` ; } if ( pxt . BrowserUtils . isLocalHost ( ) && ! pxt . Util . liveLocalizationEnabled ( ) ) { return localRequestAsync ( url ) . then ( resp => { if ( resp . statusCode == 404 ) return privateRequestAsync ( { url , method : "GET" } ) . then ( resp => { return { md : resp . text , etag : < string > resp . headers [ "etag" ] } ; } ) ; else return { md : resp . text , etag : undefined } ; } ) ; } else { const headers = etag && ! useCdnApi ( ) ? { "If-None-Match" : etag } : undefined ; return apiRequestWithCdnAsync ( { url , method : "GET" , headers } ) . then ( resp => { return { md : resp . text , etag : < string > resp . headers [ "etag" ] } ; } ) ; } } export function privateDeleteAsync ( path ) { return privateRequestAsync ( { url : path , method : "DELETE" } ) . then ( resp => resp . json ) ; } export function privatePostAsync ( path , data , forceLiveEndpoint = false ) { return privateRequestAsync ( { url : path , data : data || { } , forceLiveEndpoint } ) . then ( resp => resp . json ) ; } export function isLoggedIn ( ) { return ! ! accessToken ; } export function isNavigatorOnline ( ) { return navigator && navigator . onLine ; } export function isOnline ( ) { if ( typeof navigator !== "undefined" && isNavigatorOnline ( ) ) { _isOnline = true ; } return _isOnline ; } export function getServiceUrl ( ) { return apiRoot . replace ( /\/api\/$/ , "" ) ; } export function getUserId ( ) { let m = /^0(\w+)\./ . exec ( accessToken ) ; if ( m ) return m [ 1 ] ; return null ; } export function parseScriptId ( uri ) { const target = pxt . appTarget ; if ( ! uri || ! target . appTheme || ! target . cloud || ! target . cloud . sharing ) return undefined ; let domains = [ "makecode.com" ] ; if ( target . appTheme . embedUrl ) domains . push ( target . appTheme . embedUrl ) ; if ( target . appTheme . shareUrl ) domains . push ( target . appTheme . shareUrl ) ; domains = Util . unique ( domains , d => d ) . map ( d => Util . escapeForRegex ( Util . stripUrlProtocol ( d ) . replace ( /\/$/ , '' ) ) . toLowerCase ( ) ) ; const rx = `^((https:\/\/)?(?:${ domains . join ( '|' ) })\/)?(?:v[0-9]+\/)?(api\/oembed\?url=.*%2F([^&]*)&.*?|([a-z0-9\-_]+))$` ; const m = new RegExp ( rx , 'i' ) . exec ( uri . trim ( ) ) ; const scriptid = m && ( ! m [ 1 ] || domains . indexOf ( Util . escapeForRegex ( m [ 1 ] . replace ( /https:\/\// , '' ) . replace ( /\/$/ , '' ) ) . toLowerCase ( ) ) >= 0 ) && ( m [ 3 ] || m [ 4 ] ) ? ( m [ 3 ] ? m [ 3 ] : m [ 4 ] ) : null ; if ( ! scriptid ) return undefined ; if ( scriptid [ 0 ] == "_" && scriptid . length == 13 ) return scriptid ; if ( scriptid . length == 23 && /^[0-9\-]+$/ . test ( scriptid ) ) return scriptid ; return undefined ; } export interface JsonIdObject { kind : string ; id : string ; } export interface JsonPublication extends JsonIdObject { time : number ; } export interface JsonScript extends JsonPublication { shortid ? : string ; name : string ; description : string ; editor ? : string ; target ? : string ; targetVersion ? : string ; meta ? : JsonScriptMeta ; thumb ? : boolean ; } }