UNPKG

lokal-js

Version:

Library for interacting with Lokal.so Client REST API

239 lines (238 loc) 8.22 kB
// lokal.ts import clc from 'cli-color'; const ServerMinVersion = '0.6.0'; class Lokal { constructor(baseURL = 'http://127.0.0.1:6174') { this.baseURL = baseURL; this.basicAuth = null; this.token = null; this.debug = false; this.skip_tls = false; } setBaseURL(url) { if (url.endsWith('/')) { url = url.slice(0, -1); } this.baseURL = url; this.logDebug('Base URL set to:', url); return this; } setBasicAuth(username, password) { this.basicAuth = { username, password }; this.logDebug('Basic auth set'); return this; } skipTLSVerify() { try { process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; console.warn('it is recommended to use env NODE_EXTRA_CA_CERTS=cert.crt,cert2.pem instead of skip TLS verify'); } catch { console.warn('process.env not available, skipping...'); } return this; } setAPIToken(token) { this.token = token; this.logDebug('API token set'); return this; } debugMode(enable) { this.debug = enable; this.logDebug(`Debug mode ${enable ? 'enabled' : 'disabled'}`); return this; } logDebug(...args) { if (this.debug) { console.log(clc.yellow('[DEBUG]'), ...args); } } async request(endpoint, method = 'GET', body = null) { const headers = { 'User-Agent': 'Lokal TS - github.com/lokal-so/lokal-ts', 'Content-Type': 'application/json' }; if (this.token) { headers['X-Auth-Token'] = this.token; } const options = { method, headers }; if (this.basicAuth) { const auth = btoa(`${this.basicAuth.username}:${this.basicAuth.password}`); headers['Authorization'] = `Basic ${auth}`; } if (body) { options.body = JSON.stringify(body); } this.logDebug('Request:', { endpoint, method, headers, body }); let response; try { response = await fetch(`${this.baseURL}${endpoint}`, options); } catch (error) { if (error instanceof Error) { console.error('No Lokal client running, you may need to install Lokal Client, download at https://lokal.so/download', error.message); } this.logDebug('Request failed:', error); return; } const serverVersion = response.headers.get('Lokal-Server-Version'); this.logDebug('Server version:', serverVersion); if (!serverVersion || !this.isValidVersion(serverVersion)) { throw new Error('Your local client might be outdated, please update'); } const data = await response.json(); this.logDebug('Response:', data); return data; } isValidVersion(version) { const [major, minor, patch] = version.split('.').map(Number); const [minMajor, minMinor, minPatch] = ServerMinVersion.split('.').map(Number); if (major > minMajor) return true; if (major < minMajor) return false; if (minor > minMinor) return true; if (minor < minMinor) return false; return patch >= minPatch; } newTunnel() { return new Tunnel(this); } } class Tunnel { constructor(lokal) { this.name = ''; this.tunnel_type = 'HTTP'; this.local_address = ''; this.inspect = false; this.options = {}; this.ignoreDuplicateFlag = false; this.startupBannerFlag = false; this.lokal = lokal; } setLocalAddress(localAddress) { this.local_address = localAddress; return this; } setTunnelType(tunnelType) { this.tunnel_type = tunnelType; return this; } setInspection(inspect) { this.inspect = inspect; return this; } setLANAddress(lanAddress) { if (lanAddress) { this.address_mdns = lanAddress.replace(/\.local$/, ''); } else { this.address_mdns = ''; } return this; } setPublicAddress(publicAddress) { this.address_public = publicAddress || ''; return this; } setName(name) { this.name = name || ''; return this; } ignoreDuplicate() { this.ignoreDuplicateFlag = true; return this; } showStartupBanner() { this.startupBannerFlag = true; return this; } async create() { if (!this.address_mdns && !this.address_public) { throw new Error('Please enable either LAN address or random/custom public URL'); } const response = await this.lokal.request('/api/tunnel/start', 'POST', this); if (response.data.length === 0) { throw new Error(response.message || 'Tunnel creation failing'); } this.lokal.logDebug('Duplicate flag', this.ignoreDuplicateFlag); this.lokal.logDebug('API response', response.message); if (!response.success) { if (this.ignoreDuplicateFlag && response.message.endsWith('address is already being used')) { this.lokal.logDebug('running ignore duplicate'); this.address_public = response.data[0].address_public; this.address_mdns = response.data[0].address_mdns; this.id = response.data[0].id; if (this.startupBannerFlag) { this.showBanner(); } return this; } throw new Error(response.message || 'Tunnel creation failing'); } const tunnel = response.data[0]; this.address_public = tunnel.address_public; this.address_mdns = tunnel.address_mdns; this.id = tunnel.id; if (this.startupBannerFlag) { this.showBanner(); } return this; } async getLANAddress() { if (!this.address_mdns) { throw new Error('LAN address is not being set'); } return this.address_mdns.endsWith('.local') ? this.address_mdns : `${this.address_mdns}.local`; } async getPublicAddress() { if (!this.address_public) { throw new Error('Public address is not requested by client'); } if (this.tunnel_type !== 'HTTP' && !this.address_public.includes(':')) { await this.updatePublicURLPort(); throw new Error('Tunnel is using a random port, but it has not been assigned yet. Please try again later'); } return this.address_public; } async updatePublicURLPort() { if (!this.id) { throw new Error('Tunnel ID is not set'); } const response = await this.lokal.request(`/api/tunnel/info/${this.id}`); if (!response.success || response.data.length === 0) { throw new Error('Could not get tunnel info'); } const tunnel = response.data[0]; if (!tunnel.address_public.includes(':')) { throw new Error('Could not get assigned port'); } this.address_public = tunnel.address_public; } showBanner() { const banner = ` __ _ _ / / ___ | | ____ _| | ___ ___ / / / _ \\| |/ / _ | | / __|/ _ \\ / /__| (_) | < (_| | |_\\__ \\ (_) | \\____/\\___/|_|\\_\\__,_|_(_)___/\\___/ `; const colors = [clc.magenta, clc.blue, clc.cyan, clc.green, clc.red]; const randomColor = colors[Math.floor(Math.random() * colors.length)]; console.log(randomColor(banner)); console.log(); console.log(clc.red('Minimum Lokal Client'), `\t${ServerMinVersion}`); if (this.address_public) { console.log(clc.cyan('Public Address'), `\t\thttps://${this.address_public}`); } if (this.address_mdns) { console.log(clc.green('LAN Address'), `\t\thttps://${this.address_mdns}`); } console.log(); } } export { Lokal, Tunnel };