From caa515a2f2e3e435f2bb6481d7ad2a255eec938b Mon Sep 17 00:00:00 2001 From: Donavan Becker Date: Sun, 30 Jun 2024 16:59:28 -0500 Subject: [PATCH] add custom defined domain and specify token --- config.schema.json | 80 +++++++++++++++++++++++------ package-lock.json | 47 +++++++---------- package.json | 7 ++- src/custom.d.ts | 5 ++ src/homebridge-ui/public/index.html | 32 +++++++----- src/index.ts | 4 +- src/platform.ts | 65 +++++++++++++++-------- src/settings.ts | 6 ++- 8 files changed, 162 insertions(+), 84 deletions(-) create mode 100644 src/custom.d.ts diff --git a/config.schema.json b/config.schema.json index 9c9e5d5..c2d407c 100644 --- a/config.schema.json +++ b/config.schema.json @@ -15,11 +15,29 @@ "default": "CloudflaredTunnel", "required": true }, + "domain": { + "type": "string", + "title": "Domain", + "description": "The domain of the self specified tunnel.", + "required": false + }, + "token": { + "type": "string", + "title": "token", + "description": "The token to use for the self specified tunnel.", + "required": false, + "condition": { + "functionBody": "return (!model.url || (!model.protocol && !model.hostname && !model.port));" + } + }, "acceptCloudflareNotice": { "type": "boolean", "title": "Auto Start Tunnel Install", "required": true, - "description": "Automatically start the Cloudflared Tunnel install process." + "description": "Automatically start the Cloudflared Tunnel install process.", + "condition": { + "functionBody": "return (!model.token && !model.domain);" + } }, "url": { "type": "string", @@ -35,7 +53,9 @@ "format": "uri", "patternErrorMessage": "URL must start with http:// or https://", "validationMessage": "URL must start with http:// or https://", - "validationKeywords": ["pattern"], + "validationKeywords": [ + "pattern" + ], "validation": { "pattern": "^https?://" } @@ -48,11 +68,15 @@ "oneOf": [ { "title": "HTTP", - "enum": ["http"] + "enum": [ + "http" + ] }, { "title": "HTTPS", - "enum": ["https"] + "enum": [ + "https" + ] } ], "description": "The protocol to use for the tunnel. If you are using a URL, you do not need to specify the protocol, hostname, or port.", @@ -73,7 +97,9 @@ "validationMessage": "Hostname must be specified if not using a URL", "pattern": "^(?!\\s*$).+", "patternErrorMessage": "Hostname must be specified if not using a URL", - "validationKeywords": ["pattern"], + "validationKeywords": [ + "pattern" + ], "condition": { "functionBody": "return (!model.url);" } @@ -87,7 +113,10 @@ "minimum": 1, "maximum": 65535, "validationMessage": "Port must be between 1 and 65535", - "validationKeywords": ["minimum", "maximum"], + "validationKeywords": [ + "minimum", + "maximum" + ], "validation": { "minimum": 1, "maximum": 65535, @@ -118,28 +147,45 @@ "oneOf": [ { "title": "Default Logging", - "enum": [""] + "enum": [ + "" + ] }, { "title": "Standard Logging", - "enum": ["standard"] + "enum": [ + "standard" + ] }, { "title": "No Logging", - "enum": ["none"] + "enum": [ + "none" + ] }, { "title": "Debug Logging", - "enum": ["debug"] + "enum": [ + "debug" + ] } ] } }, - "dependentRequired": { - "protocol": ["hostname", "port"], - "hostname": ["protocol", "port"], - "port": ["protocol", "hostname"] - } + "dependentRequired": { + "protocol": [ + "hostname", + "port" + ], + "hostname": [ + "protocol", + "port" + ], + "port": [ + "protocol", + "hostname" + ] + } }, "layout": [ { @@ -148,6 +194,8 @@ "expandable": true, "expanded": false, "items": [ + "domain", + "token", "acceptCloudflareNotice", "url", "protocol", @@ -166,4 +214,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6161c3e..fc65f8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,15 +20,14 @@ "license": "ISC", "dependencies": { "@homebridge/plugin-ui-utils": "^1.0.3", + "node-cloudflared-tunnel": "^1.0.10", "untun": "^0.1.3" }, "devDependencies": { "@eslint/js": "^9.6.0", "@stylistic/eslint-plugin": "^2.3.0", - "@types/eslint__js": "^8.42.3", "@types/node": "^20.14.9", "eslint": "^9.6.0", - "globals": "^15.7.0", "homebridge": "^1.8.3", "homebridge-config-ui-x": "4.56.4", "nodemon": "^3.1.4", @@ -145,19 +144,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@eslint/js": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", @@ -1813,16 +1799,6 @@ "@types/json-schema": "*" } }, - "node_modules/@types/eslint__js": { - "version": "8.42.3", - "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", - "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -3124,6 +3100,12 @@ "node": ">= 0.8" } }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "license": "MIT" + }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -4738,9 +4720,9 @@ } }, "node_modules/globals": { - "version": "15.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.7.0.tgz", - "integrity": "sha512-ivatRXWwKC6ImcdKO7dOwXuXR5XFrdwo45qFwD7D0qOkEPzzJdLXC3BHceBdyrPOD3p1suPaWi4Y4NMm2D++AQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", "engines": { @@ -6752,6 +6734,15 @@ "node": ">= 8.0.0" } }, + "node_modules/node-cloudflared-tunnel": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/node-cloudflared-tunnel/-/node-cloudflared-tunnel-1.0.10.tgz", + "integrity": "sha512-QBTH4bcTePgiwYdK5vYZhZl7GNr3Pqnj5V3Tg1w5HvnK4VhLZe5BUuhqKvzzCA604yhoD2I1ATdnOl6axSJu/w==", + "license": "MIT", + "dependencies": { + "command-exists": "^1.2.9" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", diff --git a/package.json b/package.json index 426c9bf..775ee41 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "Cloudflared Tunnel", "name": "homebridge-cloudflared-tunnel", - "version": "1.0.3", + "version": "1.1.0", "description": "The Cloudflared Tunnel plugin allows you to run a Cloudflare-Tunnel for exposing your homebridge instance for remote access.", "author": { "name": "donavanbecker", @@ -51,15 +51,14 @@ ], "dependencies": { "@homebridge/plugin-ui-utils": "^1.0.3", - "untun": "^0.1.3" + "untun": "^0.1.3", + "node-cloudflared-tunnel": "^1.0.10" }, "devDependencies": { "@eslint/js": "^9.6.0", "@stylistic/eslint-plugin": "^2.3.0", - "@types/eslint__js": "^8.42.3", "@types/node": "^20.14.9", "eslint": "^9.6.0", - "globals": "^15.7.0", "homebridge": "^1.8.3", "homebridge-config-ui-x": "4.56.4", "nodemon": "^3.1.4", diff --git a/src/custom.d.ts b/src/custom.d.ts new file mode 100644 index 0000000..da9dff3 --- /dev/null +++ b/src/custom.d.ts @@ -0,0 +1,5 @@ +/* Copyright(C) 2023-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved. + * + * custom.d.ts: homebridge-cloudflared-tunnel platform class. + */ +declare module 'node-cloudflared-tunnel'; diff --git a/src/homebridge-ui/public/index.html b/src/homebridge-ui/public/index.html index 1c5c661..e62400c 100644 --- a/src/homebridge-ui/public/index.html +++ b/src/homebridge-ui/public/index.html @@ -1,8 +1,7 @@

homebridge-cloudflared-tunnel logo + alt="homebridge-cloudflared-tunnel logo" style="width: 40%" />

@@ -222,4 +230,4 @@
Disclaimer
homebridge.hideSpinner(); } })(); - + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c9d9ee5..d46a76b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,11 +2,11 @@ * * index.ts: homebridge-cloudflared-tunnel. */ -import { API } from 'homebridge'; - import { CloudflaredTunnelPlatform } from './platform.js'; import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'; +import type { API } from 'homebridge'; + // Register our platform with homebridge. export default (api: API): void => { diff --git a/src/platform.ts b/src/platform.ts index 4437869..1494822 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -2,11 +2,13 @@ * * platform.ts: homebridge-cloudflared-tunnel. */ -import { API, DynamicPlatformPlugin, HAP, Logging, PlatformAccessory } from 'homebridge'; -import { startTunnel, TunnelOptions } from 'untun'; +import { startTunnel } from 'untun'; import { readFileSync } from 'fs'; +import { CloudflaredTunnel } from 'node-cloudflared-tunnel'; -import { CloudflaredTunnelPlatformConfig } from './settings.js'; +import type { TunnelOptions } from 'untun'; +import type { CloudflaredTunnelPlatformConfig } from './settings.js'; +import type { API, DynamicPlatformPlugin, HAP, Logging, PlatformAccessory } from 'homebridge'; /** * HomebridgePlatform @@ -41,6 +43,8 @@ export class CloudflaredTunnelPlatform implements DynamicPlatformPlugin { // Plugin options into our config variables. this.config = { platform: 'CloudflaredTunnel', + domain: config.domain as string, + token: config.token as string, name: config.name as string, url: config.url as string, port: config.port as number, @@ -74,7 +78,14 @@ export class CloudflaredTunnelPlatform implements DynamicPlatformPlugin { log.debug('Executed didFinishLaunching callback'); // run the method to discover / register your devices as accessories try { - await this.createTunnel(); + if (this.config.domain) { + const tunnel = new CloudflaredTunnel(); + tunnel.token = this.config.token; + await this.infoLog(`Starting Tunnel with Domain: ${this.config.domain}`); + tunnel.start(); + } else { + await this.createTunnel(); + } } catch (e: any) { this.errorLog(`Failed to Start Tunnel, Error Message: ${JSON.stringify(e.message)}`); this.debugErrorLog(JSON.stringify(e)); @@ -112,7 +123,7 @@ export class CloudflaredTunnelPlatform implements DynamicPlatformPlugin { } async createTunnel() { - this.debugLog(JSON.stringify(this.config)); + await this.debugLog(JSON.stringify(this.config)); //The local server URL to tunnel. const options: TunnelOptions = { url: this.config.url, @@ -122,11 +133,11 @@ export class CloudflaredTunnelPlatform implements DynamicPlatformPlugin { verifyTLS: this.config.verifyTLS, acceptCloudflareNotice: this.config.acceptCloudflareNotice, }; - this.debugWarnLog(`Starting Tunnel with Options: ${JSON.stringify(options)}`); + await this.debugWarnLog(`Starting Tunnel with Options: ${JSON.stringify(options)}`); const autoTunnel = await startTunnel(options); if (autoTunnel) { const tunnelURL = await autoTunnel.getURL(); - this.infoLog(`Tunnel URL: ${JSON.stringify(tunnelURL)}`); + await this.infoLog(`Tunnel URL: ${JSON.stringify(tunnelURL)}`); } } @@ -185,42 +196,56 @@ export class CloudflaredTunnelPlatform implements DynamicPlatformPlugin { * If device level logging is turned on, log to log.warn * Otherwise send debug logs to log.debug */ - infoLog(...log: any[]): void { - if (this.enablingPlatfromLogging()) { + async infoLog(...log: any[]): Promise { + if (await this.enablingPlatformLogging()) { this.log.info(String(...log)); } } - warnLog(...log: any[]): void { - if (this.enablingPlatfromLogging()) { + async successLog(...log: any[]): Promise { + if (await this.enablingPlatformLogging()) { + this.log.success(String(...log)); + } + } + + async debugSuccessLog(...log: any[]): Promise { + if (await this.enablingPlatformLogging()) { + if (this.platformLogging?.includes('debug')) { + this.log.success('[DEBUG]', String(...log)); + } + } + } + + async warnLog(...log: any[]): Promise { + if (await this.enablingPlatformLogging()) { this.log.warn(String(...log)); } } - debugWarnLog(...log: any[]): void { - if (this.enablingPlatfromLogging()) { + async debugWarnLog(...log: any[]): Promise { + if (await this.enablingPlatformLogging()) { if (this.platformLogging?.includes('debug')) { this.log.warn('[DEBUG]', String(...log)); } } } - errorLog(...log: any[]): void { - if (this.enablingPlatfromLogging()) { + async errorLog(...log: any[]): Promise { + if (await this.enablingPlatformLogging()) { this.log.error(String(...log)); } } - debugErrorLog(...log: any[]): void { - if (this.enablingPlatfromLogging()) { + async debugErrorLog(...log: any[]): Promise { + if (await this.enablingPlatformLogging()) { if (this.platformLogging?.includes('debug')) { this.log.error('[DEBUG]', String(...log)); } } } - debugLog(...log: any[]): void { - if (this.enablingPlatfromLogging()) { + async debugLog(...log: any[]): Promise { + if (await this.enablingPlatformLogging()) { if (this.platformLogging === 'debugMode') { this.log.debug(String(...log)); } else if (this.platformLogging === 'debug') { @@ -229,7 +254,7 @@ export class CloudflaredTunnelPlatform implements DynamicPlatformPlugin { } } - enablingPlatfromLogging(): boolean { + async enablingPlatformLogging(): Promise { return this.platformLogging?.includes('debug') || this.platformLogging === 'standard'; } } \ No newline at end of file diff --git a/src/settings.ts b/src/settings.ts index a670fb2..b8cf95f 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -2,8 +2,8 @@ * * settings.ts: homebridge-cloudflared-tunnel. */ -import { PlatformConfig } from 'homebridge'; -import { TunnelOptions } from 'untun'; +import type { TunnelOptions } from 'untun'; +import type { PlatformConfig } from 'homebridge'; /** * This is the name of the platform that users will use to register the plugin in the Homebridge config.json */ @@ -16,6 +16,8 @@ export const PLUGIN_NAME = 'homebridge-cloudflared-tunnel'; //Config export interface CloudflaredTunnelPlatformConfig extends PlatformConfig { + domain?: string; + token?: string; url?: TunnelOptions['url']; port?: TunnelOptions['port']; hostname?: TunnelOptions['hostname'];