UNPKG

@softvisio/core

Version:
428 lines (329 loc) • 11 kB
import CacheLru from "#lib/cache/lru"; import Interval from "#lib/interval"; import isBrowser from "#lib/is-browser"; import WebSocket from "./websocket.js"; const DEFAULT_VERSION = 1; const DEFAULT_CACHE_MAX_SIZE = 10_000; const DEFAULT_CACHE_MAX_AGE = 0; export default class extends WebSocket { #protocol; #hostname; #port; #pathname = "/"; #isPersistent; #token; #locale; #version; #maxConnections; #checkCertificate; #onCall; #onAuthorization; #url; #httpUrl; #websocketsUrl; #uploadUrl; #realMaxConnections; #cache; #clearCacheOn = new Set(); constructor ( url, { token, locale, persistent, version, maxConnections, checkCertificate, onCall, onAuthorization, cache, cacheMaxSize, cacheMaxAge, clearCacheOn } = {} ) { super(); url = this._resolveUrl( url ); this.#protocol = url.protocol; this.#hostname = url.hostname; this.#port = url.port; this.#pathname = url.pathname; // token this.#token = ( token ?? decodeURIComponent( url.username ) ) || null; // locale if ( locale ) { this.#locale = locale; } else { this.#locale = url.searchParams.get( "locale" ) || null; } // persistent this.#isPersistent = persistent ?? url.searchParams.get( "persistent" ); if ( this.#isPersistent == null || this.#isPersistent === "" ) { this.#isPersistent = this.#protocol.startsWith( "ws" ); } else { this.#isPersistent = this.#isPersistent === true || this.#isPersistent === "true"; } // set protocol according to the persistent value if ( this.#isPersistent ) { if ( this.#protocol === "http:" ) this.#protocol = "ws:"; else if ( this.#protocol === "https:" ) this.#protocol = "wss:"; } else { if ( this.#protocol === "ws:" ) this.#protocol = "http:"; else if ( this.#protocol === "wss:" ) this.#protocol = "https:"; } // version this.#version = +( version ?? url.searchParams.get( "version" ) ) || DEFAULT_VERSION; if ( !Number.isInteger( this.#version ) || this.#version < 1 ) { throw TypeError( "API client version value is invalid" ); } // maxConnections this.#maxConnections = maxConnections || url.searchParams.get( "maxConnections" ); if ( !this.#maxConnections ) { this.#maxConnections = null; } else { this.#maxConnections = +this.#maxConnections; if ( !Number.isInteger( this.#maxConnections ) || this.#maxConnections < 1 ) { throw TypeError( "API client maxConnections value is invalid" ); } } // tls check certificate if ( checkCertificate === false ) { this.#checkCertificate = false; } else { this.#checkCertificate = true; } // onCall this.#onCall = onCall; // onAuthorization this.#onAuthorization = onAuthorization; // cache cacheMaxSize = +( cacheMaxSize ?? ( url.searchParams.get( "cacheMaxSize" ) || DEFAULT_CACHE_MAX_SIZE ) ); if ( !Number.isInteger( cacheMaxSize ) || cacheMaxSize <= 0 ) { throw TypeError( "API client cacheMaxSize value is invalid" ); } cacheMaxAge = url.searchParams.get( "cacheMaxAge" ) || DEFAULT_CACHE_MAX_AGE; this.#cache = cache || new CacheLru( { "maxSize": cacheMaxSize, "maxAge": cacheMaxAge, } ); // cache drop events new Set( clearCacheOn ?? url.searchParams.getAll( "clearCacheOn" ) ).forEach( name => { name.split( "," ).forEach( name => { name = name.trim(); if ( this.#clearCacheOn.has( name ) ) return; this.#clearCacheOn.add( name ); this.on( name, () => this.#cache.clear() ); } ); } ); } // properties get api () { return this; } get protocol () { return this.#protocol; } get hostname () { return this.#hostname; } get port () { return this.#port; } get pathname () { return this.#pathname; } get url () { if ( !this.#url ) { if ( this.#isPersistent ) { this.#url = this.websocketsUrl; } else { this.#url = this.httpUrl; } } return this.#url; } get httpUrl () { if ( !this.#httpUrl ) { const url = this.#buildUrl(); if ( url.protocol === "ws:" ) { url.protocol = "http:"; } else if ( url.protocol === "wss:" ) { url.protocol = "https:"; } this.#httpUrl = url.href; } return this.#httpUrl; } get websocketsUrl () { if ( !this.#websocketsUrl ) { const url = this.#buildUrl(); if ( url.protocol === "http:" ) { url.protocol = "ws:"; } else if ( url.protocol === "https:" ) { url.protocol = "wss:"; } if ( this.#maxConnections ) { url.searchParams.set( "maxConnections", this.#maxConnections ); } this.#websocketsUrl = url.href; } return this.#websocketsUrl; } get token () { return this.#token; } set token ( value ) { value ||= null; // not changed if ( this.#token === value ) return; this.#token = value; this.#optionsUpdated(); this._tokenUpdated(); } get locale () { return this.#locale; } set locale ( value ) { value ||= null; // not changed if ( this.#locale === value ) return; this.#locale = value; this.#optionsUpdated(); this._tokenUpdated(); } get isPersistent () { return this.#isPersistent; } get version () { return this.#version; } get maxConnections () { return this.#maxConnections; } get realMaxConnections () { if ( this.#realMaxConnections == null ) { // max connections always 1 under the browser if ( isBrowser ) { this.#realMaxConnections = 1; } else { this.#realMaxConnections = this.maxConnections || Infinity; } } return this.#realMaxConnections; } get checkCertificate () { return this.#checkCertificate; } get cache () { return this.#cache; } get onCall () { return this.#onCall; } get onAuthorization () { return this.#onAuthorization; } // public toString () { return this.url; } toJSON () { return this.url; } prepateMethodName ( method, absolute ) { if ( method.startsWith( "/" ) ) { if ( absolute ) { return method; } else { return method.slice( 1 ); } } else { if ( absolute ) { return `/v${ this.version }/${ method }`; } else { return `v${ this.version }/${ method }`; } } } upload ( method, ...args ) { var options; if ( typeof method === "object" ) { ( { method, args, ...options } = method ); } return new this.Upload( this, this.#getUploadUrl(), method, args, options ); } async cachedCall ( method, ...args ) { var key, maxAge, signal; if ( typeof method === "object" ) { ( { method, args, key, maxAge, signal } = method ); } key = method + "/" + ( key ?? "" ); var res = this.cache.get( key ); if ( res ) return res; res = await this.call( { method, args, signal, } ); this.cacheResult( res, key, maxAge ); return res; } cacheResult ( res, key, maxAge ) { // cache successful responses if ( key && res.ok && !res.meta[ "cache-control-no-cache" ] ) { if ( maxAge ) maxAge = Interval.new( maxAge ).toMilliseconds(); if ( res.meta[ "cache-control-expires" ] ) { const remoteMaxAge = Date.parse( res.meta[ "cache-control-expires" ] ) - Date.now(); if ( !Number.isNaN( remoteMaxAge ) ) { if ( !maxAge ) { maxAge = remoteMaxAge; } else if ( remoteMaxAge < maxAge ) { maxAge = remoteMaxAge; } } } if ( res.meta[ "cache-control-max-age" ] ) { const remoteMaxAge = new Interval( res.meta[ "cache-control-max-age" ] ).toMilliseconds(); if ( !maxAge ) { maxAge = remoteMaxAge; } else if ( remoteMaxAge < maxAge ) { maxAge = remoteMaxAge; } } this.cache.set( key, res, maxAge ); } } // private #optionsUpdated () { this.#url = null; this.#httpUrl = null; this.#websocketsUrl = null; this.#uploadUrl = null; } #buildUrl () { const url = new URL( this.#protocol + "//" + this.#hostname ); url.port = this.#port; url.pathname = this.#pathname; if ( this.#token ) url.username = this.#token; if ( this.#locale ) url.searchParams.set( "locale", this.#locale ); if ( this.#version !== DEFAULT_VERSION ) url.searchParams.set( "version", this.#version ); if ( this.#cache.maxSize !== DEFAULT_CACHE_MAX_SIZE ) url.searchParams.set( "cacheMaxSize", +this.#cache.maxSize ); if ( this.#cache.maxAge !== DEFAULT_CACHE_MAX_AGE ) url.searchParams.set( "cacheMaxAge", new Interval( this.#cache.maxAge ) + "" ); if ( this.#clearCacheOn.size ) { url.searchParams.append( "clearCacheOn", [ ...this.#clearCacheOn ].join( "," ) ); } url.searchParams.sort(); return url; } #getUploadUrl () { if ( !this.#uploadUrl ) { const url = this.#buildUrl(); if ( url.protocol === "ws:" ) url.protocol = "http:"; else if ( url.protocol === "wss:" ) url.protocol = "https:"; url.username = ""; url.search = ""; this.#uploadUrl = url; } return this.#uploadUrl; } }