From eb242ce953681f882ffff09dea7aaf4a0d29fa88 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Wed, 1 Jul 2026 12:36:05 +0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20style:=20add=20Prettier=20format?= =?UTF-8?q?ting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierignore | 5 + .prettierrc | 5 + bun.lock | 17 +- configs_example/UpdaterConfig.example.json | 90 +-- package.json | 3 + src/index.ts | 4 +- src/libs/Logger.ts | 80 ++- src/providers/App.ts | 34 +- src/providers/Configuration.ts | 62 +- src/providers/Daemon.ts | 643 +++++++++++---------- src/providers/Environment.ts | 56 +- tsconfig.json | 198 +++---- 12 files changed, 627 insertions(+), 570 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..fc575bc --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +node_modules +dist +configs +coverage +bun.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b68d0e1 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 100, + "singleQuote": false, + "trailingComma": "all" +} diff --git a/bun.lock b/bun.lock index 2863505..4a3489a 100644 --- a/bun.lock +++ b/bun.lock @@ -5,15 +5,16 @@ "": { "name": "cloudflare-ddns-updater", "dependencies": { - "axios": "latest", - "dotenv": "latest", - "validator": "latest", - "winston": "latest", + "axios": "^1.18.1", + "dotenv": "^17.4.2", + "validator": "^13.15.35", + "winston": "3.19.0", }, "devDependencies": { - "@types/node": "latest", - "@types/validator": "latest", - "typescript": "latest", + "@types/node": "^26.0.1", + "@types/validator": "^13.15.10", + "prettier": "^3.9.4", + "typescript": "^6.0.3", }, }, }, @@ -110,6 +111,8 @@ "one-time": ["one-time@1.0.0", "", { "dependencies": { "fn.name": "1.x.x" } }, "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="], + "prettier": ["prettier@3.9.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg=="], + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], "readable-stream": ["readable-stream@3.6.0", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA=="], diff --git a/configs_example/UpdaterConfig.example.json b/configs_example/UpdaterConfig.example.json index 4b474cd..4a0173a 100644 --- a/configs_example/UpdaterConfig.example.json +++ b/configs_example/UpdaterConfig.example.json @@ -1,46 +1,46 @@ [ - { - "token": "{ENV_TOKEN:EXAMPLE_SITE_TOKEN}", - "updateInterval": 60, - "zone": [ - { - "id": "zone-id-1", - "type": "A", - "name": "example.com", - "content": "{CURRENT_IPv4}", - "ttl": 1, - "proxied": false - }, - { - "id": "zone-id-2", - "type": "AAAA", - "name": "another.example.com", - "content": "{CURRENT_IPv6}", - "ttl": 1, - "proxied": false - } - ] - }, - { - "token": "hard-code-cloudflare-token-2", - "updateInterval": 60, - "zone": [ - { - "id": "zone-id-1", - "type": "A", - "name": "example.com", - "content": "{CURRENT_IPv4}", - "ttl": 1, - "proxied": false - }, - { - "id": "zone-id-2", - "type": "A", - "name": "another.example.com", - "content": "{CURRENT_IPv6}", - "ttl": 1, - "proxied": false - } - ] - } -] \ No newline at end of file + { + "token": "{ENV_TOKEN:EXAMPLE_SITE_TOKEN}", + "updateInterval": 60, + "zone": [ + { + "id": "zone-id-1", + "type": "A", + "name": "example.com", + "content": "{CURRENT_IPv4}", + "ttl": 1, + "proxied": false + }, + { + "id": "zone-id-2", + "type": "AAAA", + "name": "another.example.com", + "content": "{CURRENT_IPv6}", + "ttl": 1, + "proxied": false + } + ] + }, + { + "token": "hard-code-cloudflare-token-2", + "updateInterval": 60, + "zone": [ + { + "id": "zone-id-1", + "type": "A", + "name": "example.com", + "content": "{CURRENT_IPv4}", + "ttl": 1, + "proxied": false + }, + { + "id": "zone-id-2", + "type": "A", + "name": "another.example.com", + "content": "{CURRENT_IPv6}", + "ttl": 1, + "proxied": false + } + ] + } +] diff --git a/package.json b/package.json index 30794a8..82a85c4 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,13 @@ "devDependencies": { "@types/node": "^26.0.1", "@types/validator": "^13.15.10", + "prettier": "^3.9.4", "typescript": "^6.0.3" }, "scripts": { "typecheck": "tsc --noEmit", + "format": "prettier --write .", + "format:check": "prettier --check .", "dev": "bun --watch src/index.ts", "start": "bun src/index.ts", "build": "bun run typecheck", diff --git a/src/index.ts b/src/index.ts index 05cbff3..5cf1de2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ -import App from './providers/App'; +import App from "./providers/App"; App.loadConfig(); App.loadENV(); App.loadDaemon(); -export default App; \ No newline at end of file +export default App; diff --git a/src/libs/Logger.ts b/src/libs/Logger.ts index 74ee904..f1fce91 100644 --- a/src/libs/Logger.ts +++ b/src/libs/Logger.ts @@ -1,64 +1,62 @@ -import winston from 'winston'; +import winston from "winston"; const levels = { - critical: 0, - error: 1, - alert: 2, - warn: 3, - info: 4, - http: 5, - debug: 6, + critical: 0, + error: 1, + alert: 2, + warn: 3, + info: 4, + http: 5, + debug: 6, }; const level = () => { - const env = process.env.NODE_ENV || 'development'; - const isDevelopment = env === 'development'; - return isDevelopment ? 'debug' : 'info'; + const env = process.env.NODE_ENV || "development"; + const isDevelopment = env === "development"; + return isDevelopment ? "debug" : "info"; }; const colors = { - critical: 'red', - error: 'red', - alert: 'red', - warn: 'yellow', - info: 'green', - http: 'magenta', - debug: 'white', + critical: "red", + error: "red", + alert: "red", + warn: "yellow", + info: "green", + http: "magenta", + debug: "white", }; winston.addColors(colors); const consoleFormat = winston.format.combine( - winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }), - winston.format.colorize({ all: true }), - winston.format.printf( - (info) => `[${info.timestamp}] [${info.level}] ${info.message}` - ) + winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }), + winston.format.colorize({ all: true }), + winston.format.printf((info) => `[${info.timestamp}] [${info.level}] ${info.message}`), ); const format = winston.format.combine( - winston.format.timestamp({ - format: new Date().toISOString() - }), - winston.format.json() -) + winston.format.timestamp({ + format: new Date().toISOString(), + }), + winston.format.json(), +); const transports = [ - new winston.transports.Console({ - format: consoleFormat - }), - new winston.transports.File({ - filename: 'logs/error.log', - level: 'error', - }), - new winston.transports.File({ filename: 'logs/all.log' }), + new winston.transports.Console({ + format: consoleFormat, + }), + new winston.transports.File({ + filename: "logs/error.log", + level: "error", + }), + new winston.transports.File({ filename: "logs/all.log" }), ]; const Logger = winston.createLogger({ - level: level(), - levels, - format, - transports, + level: level(), + levels, + format, + transports, }); -export default Logger; \ No newline at end of file +export default Logger; diff --git a/src/providers/App.ts b/src/providers/App.ts index 57433d0..de6333c 100644 --- a/src/providers/App.ts +++ b/src/providers/App.ts @@ -1,23 +1,23 @@ -import Logger from '../libs/Logger'; +import Logger from "../libs/Logger"; -import Environment from './Environment'; -import Configuration from './Configuration'; -import Daemon from './Daemon'; +import Environment from "./Environment"; +import Configuration from "./Configuration"; +import Daemon from "./Daemon"; class App { - public loadConfig(): void { - Logger.log('info', 'Loading configuration'); - Configuration.init(); - } + public loadConfig(): void { + Logger.log("info", "Loading configuration"); + Configuration.init(); + } - public loadENV(): void { - Logger.log('info', 'Loading environment'); - Environment.init(); - } - public loadDaemon() : void { - Logger.log('info', 'Loading daemon'); - Daemon.init(); - } + public loadENV(): void { + Logger.log("info", "Loading environment"); + Environment.init(); + } + public loadDaemon(): void { + Logger.log("info", "Loading daemon"); + Daemon.init(); + } } -export default new App; \ No newline at end of file +export default new App(); diff --git a/src/providers/Configuration.ts b/src/providers/Configuration.ts index c5aacaf..96bd587 100644 --- a/src/providers/Configuration.ts +++ b/src/providers/Configuration.ts @@ -3,44 +3,42 @@ import path from "path"; const ConfigurationData: any = []; class Configuration { - - public init(): void { - const dir = path.join(process.cwd(), 'configs/'); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - this.copyExampleIfNotExists("UpdaterConfig.json"); - - this.loadConfig("UpdaterConfig.json"); + public init(): void { + const dir = path.join(process.cwd(), "configs/"); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); } + this.copyExampleIfNotExists("UpdaterConfig.json"); - public loadConfig(configFileName: string): void { - const dir = path.join(process.cwd(), 'configs/'); - if (!fs.existsSync(path.join(dir, configFileName))) - throw new Error(`Config file ${configFileName} does not exist`); + this.loadConfig("UpdaterConfig.json"); + } - const config = JSON.parse(fs.readFileSync(path.join(dir, configFileName)).toString()); - ConfigurationData[configFileName.replace(/\.[^/.]+$/, "")] = config; + public loadConfig(configFileName: string): void { + const dir = path.join(process.cwd(), "configs/"); + if (!fs.existsSync(path.join(dir, configFileName))) + throw new Error(`Config file ${configFileName} does not exist`); + + const config = JSON.parse(fs.readFileSync(path.join(dir, configFileName)).toString()); + ConfigurationData[configFileName.replace(/\.[^/.]+$/, "")] = config; + } + + public getConfig(key?: string) { + if (!key) return ConfigurationData; + else { + if (ConfigurationData[key]) return ConfigurationData[key]; + else throw new Error(`No configuration found for ${key}`); } + } - public getConfig(key?: string) { - if(!key) - return ConfigurationData; - else { - if(ConfigurationData[key]) - return ConfigurationData[key]; - else - throw new Error(`No configuration found for ${key}`); - } + private copyExampleIfNotExists(file: string): void { + const dir = path.join(process.cwd(), "configs/"); + if (!fs.existsSync(path.join(dir, file))) { + fs.copyFileSync( + path.join(process.cwd(), "configs_example/", `${file.replace(".json", ".example.json")}`), + path.join(dir, file), + ); } - - private copyExampleIfNotExists(file: string): void { - const dir = path.join(process.cwd(), 'configs/'); - if (!fs.existsSync(path.join(dir, file))) { - fs.copyFileSync(path.join(process.cwd(), 'configs_example/', `${file.replace('.json', '.example.json')}`), path.join(dir, file)); - } - } - + } } export default new Configuration(); diff --git a/src/providers/Daemon.ts b/src/providers/Daemon.ts index 4db8190..2b1c2db 100644 --- a/src/providers/Daemon.ts +++ b/src/providers/Daemon.ts @@ -3,353 +3,406 @@ import Environment from "./Environment"; import Configuration from "./Configuration"; import axios from "axios"; -import validator from 'validator'; +import validator from "validator"; interface CloudflareConfig { - token: string, - updateInterval: number, - zone: Array + token: string; + updateInterval: number; + zone: Array; } interface ZoneConfig { - id: string, - type: string, - name: string, - content: string, - ttl: number, - proxied: boolean + id: string; + type: string; + name: string; + content: string; + ttl: number; + proxied: boolean; } class Deamon { + private config: Array = []; - private config: Array = []; + constructor() {} - constructor () { + public init(): void { + const _config = Configuration.getConfig("UpdaterConfig"); + if (!_config) { + Logger.error("No configuration found"); + return; } - public init(): void { - const _config = Configuration.getConfig("UpdaterConfig"); - if(!_config) { - Logger.error('No configuration found') - return; - } - - for(let cloudflareConfig of _config) { - if(!this.isCloudflareConfig(cloudflareConfig)) { - Logger.error(`Invalid configuration for cloudflare: ${JSON.stringify(cloudflareConfig)}`); - continue; - } + for (let cloudflareConfig of _config) { + if (!this.isCloudflareConfig(cloudflareConfig)) { + Logger.error(`Invalid configuration for cloudflare: ${JSON.stringify(cloudflareConfig)}`); + continue; + } - this.config.push(cloudflareConfig); - } - - Logger.info(`Loaded ${this.config.length} cloudflare configs`); - this.start(); + this.config.push(cloudflareConfig); } - public start(): void { - Logger.info('Starting deamon'); - for(const cloudflareConfig of this.config) { - this.update(cloudflareConfig); - setInterval(() => this.update(cloudflareConfig), cloudflareConfig.updateInterval * 1000); - } + Logger.info(`Loaded ${this.config.length} cloudflare configs`); + this.start(); + } + + public start(): void { + Logger.info("Starting deamon"); + for (const cloudflareConfig of this.config) { + this.update(cloudflareConfig); + setInterval(() => this.update(cloudflareConfig), cloudflareConfig.updateInterval * 1000); + } + } + + private async update(cloudflareConfig: CloudflareConfig) { + let token = cloudflareConfig.token; + + // Replace placeholders env with value + if (this.isEnviromentTokenPlaceholder(token)) { + const envTokenName = this.parseEnvironmentTokenPlaceholderName(token)!; + if (process.env[envTokenName]) { + token = process.env[envTokenName]!; + } else { + Logger.error(`Environment variable ${envTokenName} not found`); + return; + } } - private async update(cloudflareConfig: CloudflareConfig) { + let IPv4, IPv6; + try { + IPv4 = await this.getCurrentIPv4(); + IPv6 = await this.getCurrentIPv6(); + } catch (err) { + Logger.error(`Unable to fetch ip address: ${err}`); + return; + } - let token = cloudflareConfig.token; + IPv4 && Logger.info(`Current IPv4 address: ${IPv4}`); + IPv6 && Logger.info(`Current IPv6 address: ${IPv6}`); - // Replace placeholders env with value - if(this.isEnviromentTokenPlaceholder(token)) { - const envTokenName = this.parseEnvironmentTokenPlaceholderName(token)!; - if(process.env[envTokenName]) { - token = process.env[envTokenName]!; - } - else { - Logger.error(`Environment variable ${envTokenName} not found`); - return; - } + for (const zone of cloudflareConfig.zone) { + const api = new CloudflareAPI(token, zone.id); + const records = await api + .getRecord({ + type: zone.type, + name: zone.name, + }) + .catch((err) => { + Logger.error(`Unable to get records for zone ${zone.id}`); + return err; + }); + + if (records instanceof Error) { + continue; + } + + // No records found, create it + if (!records || records.length === 0) { + const newContent = zone.content + .replaceAll("{CURRENT_IPv4}", IPv4) + .replaceAll("{CURRENT_IPv6}", IPv6); + + const result = await api + .createRecord({ + type: zone.type, + name: zone.name, + content: IPv4, + ttl: zone.ttl, + proxied: zone.proxied, + }) + .catch((err) => { + Logger.error(`Unable to create record: ${err.message}`); + return err; + }); + + if (records instanceof Error) { + continue; } - let IPv4, IPv6; - try { - IPv4 = await this.getCurrentIPv4(); - IPv6 = await this.getCurrentIPv6(); - } catch(err) { - Logger.error(`Unable to fetch ip address: ${err}`); - return; - } + if (result) Logger.info(`Created [${zone.type}] (${zone.name} -> ${newContent})`); + } - IPv4 && Logger.info(`Current IPv4 address: ${IPv4}`); - IPv6 && Logger.info(`Current IPv6 address: ${IPv6}`); - - for(const zone of cloudflareConfig.zone) { - const api = new CloudflareAPI(token, zone.id); - const records = await api.getRecord({ - type: zone.type, - name: zone.name - }).catch(err => { - Logger.error(`Unable to get records for zone ${zone.id}`); - return err; + // Only 1 matching records found + else if (records.length === 1) { + const record = records[0]; + const newContent = zone.content + .replaceAll("{CURRENT_IPv4}", IPv4) + .replaceAll("{CURRENT_IPv6}", IPv6); + + // Check if the ip is the same + if (record.content !== IPv4 && record.content !== IPv6) { + const updateResult = await api + .updateRecord({ + record_id: record.id, + type: zone.type, + name: zone.name, + ttl: zone.ttl, + content: newContent, + proxied: zone.proxied, + }) + .catch((err) => { + Logger.error(`Unable to update record: ${err.message}`); + return err; }); - if(records instanceof Error) { - continue; - } + if (records instanceof Error) { + continue; + } - // No records found, create it - if(!records || records.length === 0) { - const newContent = zone.content.replaceAll("{CURRENT_IPv4}", IPv4).replaceAll("{CURRENT_IPv6}", IPv6); - - const result = await api.createRecord({ - type: zone.type, - name: zone.name, - content: IPv4, - ttl: zone.ttl, - proxied: zone.proxied - }).catch(err => { - Logger.error(`Unable to create record: ${err.message}`); - return err; - }); - - if(records instanceof Error) { - continue; - } - - if(result) - Logger.info(`Created [${zone.type}] (${zone.name} -> ${newContent})`); - } - - // Only 1 matching records found - else if(records.length === 1) { - const record = records[0]; - const newContent = zone.content.replaceAll("{CURRENT_IPv4}", IPv4).replaceAll("{CURRENT_IPv6}", IPv6); - - // Check if the ip is the same - if(record.content !== IPv4 && record.content !== IPv6) { - const updateResult = await api.updateRecord({ - record_id: record.id, - type: zone.type, - name: zone.name, - ttl: zone.ttl, - content: newContent, - proxied: zone.proxied - }).catch(err => { - Logger.error(`Unable to update record: ${err.message}`); - return err; - }); - - if(records instanceof Error) { - continue; - } - - if(updateResult) - Logger.info(`[${zone.type}] (${zone.name} -> ${record.content}) updated to (${zone.name} -> ${newContent})`); - } - else if( - record.ttl !== zone.ttl || - record.proxied !== zone.proxied - ) { - const updateResult = await api.updateRecord({ - record_id: record.id, - type: zone.type, - name: zone.name, - ttl: zone.ttl, - content: newContent, - proxied: zone.proxied - }).catch(err => { - Logger.error(`Unable to update record: ${err.message}`); - console.log(err); - return; - }); - - if(updateResult) - Logger.info(`[${zone.type}] (${zone.name} -> ${record.content}) updated proxy status/ttl (TTL: ${record.ttl} -> ${zone.ttl}) (Proxy status: ${record.proxied} -> ${zone.proxied})`); - } - - else { - Logger.info(`[${zone.type}] (${zone.name} -> ${record.content}) already up to date`); - continue; - } - - } - - // Many records found - else if(records.length > 1) { - Logger.error(`Multiple records found for ${zone.type} (${zone.name}) (Multiple records are not supported right now)`); - continue; - } + if (updateResult) + Logger.info( + `[${zone.type}] (${zone.name} -> ${record.content}) updated to (${zone.name} -> ${newContent})`, + ); + } else if (record.ttl !== zone.ttl || record.proxied !== zone.proxied) { + const updateResult = await api + .updateRecord({ + record_id: record.id, + type: zone.type, + name: zone.name, + ttl: zone.ttl, + content: newContent, + proxied: zone.proxied, + }) + .catch((err) => { + Logger.error(`Unable to update record: ${err.message}`); + console.log(err); + return; + }); + if (updateResult) + Logger.info( + `[${zone.type}] (${zone.name} -> ${record.content}) updated proxy status/ttl (TTL: ${record.ttl} -> ${zone.ttl}) (Proxy status: ${record.proxied} -> ${zone.proxied})`, + ); + } else { + Logger.info(`[${zone.type}] (${zone.name} -> ${record.content}) already up to date`); + continue; } + } + + // Many records found + else if (records.length > 1) { + Logger.error( + `Multiple records found for ${zone.type} (${zone.name}) (Multiple records are not supported right now)`, + ); + continue; + } + } + } + + private isCloudflareConfig(object: any): object is CloudflareConfig { + if (!this.arraysEqual(Object.keys(object), ["token", "updateInterval", "zone"])) return false; + + const res = + object && + object.token && + typeof object.token == "string" && + object.updateInterval && + typeof object.updateInterval == "number"; + + for (let zone of object.zone) { + if (!this.isZoneConfig(zone)) return false; } - private isCloudflareConfig(object: any): object is CloudflareConfig { - - if(!this.arraysEqual(Object.keys(object), ['token', 'updateInterval', 'zone'])) - return false; + if (!res) return false; - const res = object && - object.token && typeof(object.token) == 'string' && - object.updateInterval && typeof(object.updateInterval) == 'number'; + const token = object.token as string; - for(let zone of object.zone) { - if(!this.isZoneConfig(zone)) - return false; - } + // Process the env token + if (this.isEnviromentTokenPlaceholder(token)) { + const envTokenName = this.parseEnvironmentTokenPlaceholderName(token)!; + const envValue = process.env[envTokenName]; - if(!res) return false; - - const token = object.token as string; - - // Process the env token - if(this.isEnviromentTokenPlaceholder(token)) { - const envTokenName = this.parseEnvironmentTokenPlaceholderName(token)!; - const envValue = process.env[envTokenName]; - - if(!envValue) { - Logger.error(`Environment variable ${envTokenName} not found`); - return false; - } - } - - return true; + if (!envValue) { + Logger.error(`Environment variable ${envTokenName} not found`); + return false; + } } - private isZoneConfig(object: any): object is ZoneConfig { - - if(!this.arraysEqual(Object.keys(object), ['id', 'type', 'name', 'content', 'ttl', 'proxied'])) - return false; + return true; + } + private isZoneConfig(object: any): object is ZoneConfig { + if (!this.arraysEqual(Object.keys(object), ["id", "type", "name", "content", "ttl", "proxied"])) + return false; - const res = object && - object.id && typeof(object.id) == 'string' && - object.type && typeof(object.type) == 'string' && - object.name && typeof(object.name) == 'string' && - object.content && typeof(object.content) == 'string' && - object.ttl && typeof(object.ttl) == 'number' && - typeof(object.proxied) == 'boolean'; + const res = + object && + object.id && + typeof object.id == "string" && + object.type && + typeof object.type == "string" && + object.name && + typeof object.name == "string" && + object.content && + typeof object.content == "string" && + object.ttl && + typeof object.ttl == "number" && + typeof object.proxied == "boolean"; - if(!res) return false; + if (!res) return false; - return true; + return true; + } + + private isEnviromentTokenPlaceholder(token: string) { + return token.startsWith("{ENV_TOKEN:") && token.endsWith("}"); + } + + private parseEnvironmentTokenPlaceholderName(token: string) { + if (!this.isEnviromentTokenPlaceholder(token)) return null; + return token.split("{ENV_TOKEN:")[1].slice(0, -1); + } + + private async getCurrentIPv4() { + const response = await axios.get("https://api.ipify.org?format=json"); + + if (!response.data.ip) throw new Error("Unable to fetch ip address"); + + if (!validator.isIP(response.data.ip, 4)) throw new Error("Invalid IP"); + + return response.data.ip; + } + + private async getCurrentIPv6() { + const response = await axios.get("https://api64.ipify.org/?format=json"); + + if (!response.data.ip) throw new Error("Unable to fetch ip address"); + + if (!validator.isIP(response.data.ip, 6)) return null; + + return response.data.ip; + } + + private arraysEqual(a: any, b: any) { + if (a === b) return true; + if (a == null || b == null) return false; + if (a.length !== b.length) return false; + + for (var i = 0; i < a.length; ++i) { + if (a[i] !== b[i]) return false; } - - private isEnviromentTokenPlaceholder(token: string) { - return token.startsWith('{ENV_TOKEN:') && token.endsWith('}'); - } - - private parseEnvironmentTokenPlaceholderName(token: string) { - if(!this.isEnviromentTokenPlaceholder(token)) return null; - return token.split('{ENV_TOKEN:')[1].slice(0, -1); - } - - private async getCurrentIPv4() { - const response = await axios.get("https://api.ipify.org?format=json"); - - if(!response.data.ip) - throw new Error("Unable to fetch ip address"); - - if(!validator.isIP(response.data.ip, 4)) - throw new Error("Invalid IP"); - - return response.data.ip; - } - - private async getCurrentIPv6() { - const response = await axios.get("https://api64.ipify.org/?format=json"); - - if(!response.data.ip) - throw new Error("Unable to fetch ip address"); - - if(!validator.isIP(response.data.ip, 6)) - return null; - - return response.data.ip; - } - - private arraysEqual(a: any, b: any) { - if (a === b) return true; - if (a == null || b == null) return false; - if (a.length !== b.length) return false; - - for (var i = 0; i < a.length; ++i) { - if (a[i] !== b[i]) return false; - } - return true; - } - + return true; + } } class CloudflareAPI { - private token: string; - private zoneId: string; + private token: string; + private zoneId: string; - constructor(token: string, zoneId: string) { - this.token = token; - this.zoneId = zoneId; - } + constructor(token: string, zoneId: string) { + this.token = token; + this.zoneId = zoneId; + } - public async getRecord({ name, type, content, proxied, page }: { name?: string, type?: string, content?: string, proxied?: boolean, page?: number }) { - const response = await axios.get(`https://api.cloudflare.com/client/v4/zones/${this.zoneId}/dns_records`, { - params: { - name, - type, - perPage: 5000, - content, - proxied, - page - }, - headers: { - 'Authorization': `Bearer ${this.token}`, - 'Content-Type': 'application/json' - } - }); + public async getRecord({ + name, + type, + content, + proxied, + page, + }: { + name?: string; + type?: string; + content?: string; + proxied?: boolean; + page?: number; + }) { + const response = await axios.get( + `https://api.cloudflare.com/client/v4/zones/${this.zoneId}/dns_records`, + { + params: { + name, + type, + perPage: 5000, + content, + proxied, + page, + }, + headers: { + Authorization: `Bearer ${this.token}`, + "Content-Type": "application/json", + }, + }, + ); - if(response.data.success !== true) - throw new Error(`Unable to fetch record: ${response.data.errors[0].message}`); + if (response.data.success !== true) + throw new Error(`Unable to fetch record: ${response.data.errors[0].message}`); - return response.data.result; - } + return response.data.result; + } - public async createRecord ({ name, type, content, ttl, proxied }: { name?: string, type?: string, content?: string, ttl: number, proxied?: boolean }) { - const response = await axios.post(`https://api.cloudflare.com/client/v4/zones/${this.zoneId}/dns_records`, { - type, - name, - content, - ttl, - proxied - }, { - headers: { - 'Authorization': `Bearer ${this.token}`, - 'Content-Type': 'application/json' - } - }); + public async createRecord({ + name, + type, + content, + ttl, + proxied, + }: { + name?: string; + type?: string; + content?: string; + ttl: number; + proxied?: boolean; + }) { + const response = await axios.post( + `https://api.cloudflare.com/client/v4/zones/${this.zoneId}/dns_records`, + { + type, + name, + content, + ttl, + proxied, + }, + { + headers: { + Authorization: `Bearer ${this.token}`, + "Content-Type": "application/json", + }, + }, + ); - if(response.data.success !== true) - throw new Error(`Unable to create record: ${response.data.errors[0].message}`); + if (response.data.success !== true) + throw new Error(`Unable to create record: ${response.data.errors[0].message}`); - return response.data.result; - } + return response.data.result; + } - public async updateRecord({ record_id, name, type, content, ttl, proxied }: { record_id: string, name?: string, type?: string, content?: string, ttl: number, proxied?: boolean }) { - const response = await axios.put(`https://api.cloudflare.com/client/v4/zones/${this.zoneId}/dns_records/${record_id}`, { - type, - name, - content, - ttl, - proxied - }, { - headers: { - 'Authorization': `Bearer ${this.token}`, - 'Content-Type': 'application/json' - } - }); + public async updateRecord({ + record_id, + name, + type, + content, + ttl, + proxied, + }: { + record_id: string; + name?: string; + type?: string; + content?: string; + ttl: number; + proxied?: boolean; + }) { + const response = await axios.put( + `https://api.cloudflare.com/client/v4/zones/${this.zoneId}/dns_records/${record_id}`, + { + type, + name, + content, + ttl, + proxied, + }, + { + headers: { + Authorization: `Bearer ${this.token}`, + "Content-Type": "application/json", + }, + }, + ); - if(response.data.success !== true) - throw new Error(`Unable to update record: ${response.data.errors[0].message}`); + if (response.data.success !== true) + throw new Error(`Unable to update record: ${response.data.errors[0].message}`); - return response.data.result; - } + return response.data.result; + } } export default new Deamon(); diff --git a/src/providers/Environment.ts b/src/providers/Environment.ts index 72ac518..171ce72 100644 --- a/src/providers/Environment.ts +++ b/src/providers/Environment.ts @@ -3,49 +3,41 @@ import * as dotenv from "dotenv"; import Logger from "../libs/Logger"; -const requiredENV = [ - 'NODE_ENV', -]; +const requiredENV = ["NODE_ENV"]; class Environment { + public init(): void { + dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - public init(): void { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - - for (let param of requiredENV) { - if (this.isUndefinedOrEmpty(process.env[param])) - throw new Error(`.env ${param} is undefined`); - } - - // NODE_ENV Checks - if (this.get().NODE_ENV != "production" && this.get().NODE_ENV != "development") - throw new Error('.env NODE_ENV must be either "production" or "development"'); - - Logger.log('info', `Running in ${process.env.NODE_ENV} environment`); + for (let param of requiredENV) { + if (this.isUndefinedOrEmpty(process.env[param])) + throw new Error(`.env ${param} is undefined`); } - public get(): any { + // NODE_ENV Checks + if (this.get().NODE_ENV != "production" && this.get().NODE_ENV != "development") + throw new Error('.env NODE_ENV must be either "production" or "development"'); - const NODE_ENV = process.env.NODE_ENV; - - return { - NODE_ENV - }; - } + Logger.log("info", `Running in ${process.env.NODE_ENV} environment`); + } - private isUndefinedOrEmpty(value: String | undefined): boolean { - if(typeof value === 'undefined') - return true; + public get(): any { + const NODE_ENV = process.env.NODE_ENV; - if(value === undefined) - return true; + return { + NODE_ENV, + }; + } - if(value === '') - return true; + private isUndefinedOrEmpty(value: String | undefined): boolean { + if (typeof value === "undefined") return true; - return false; - } + if (value === undefined) return true; + if (value === "") return true; + + return false; + } } export default new Environment(); diff --git a/tsconfig.json b/tsconfig.json index 3ddfed6..1ed9b50 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,108 +1,108 @@ { - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ - /* Projects */ - // "incremental": true, /* Enable incremental compilation */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "ES2021", - /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ - // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + /* Language and Environment */ + "target": "ES2021", + /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - /* Modules */ - "module": "commonjs", - /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "resolveJsonModule": true, /* Enable importing .json files */ - // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + /* Modules */ + "module": "commonjs", + /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - "sourceMap": true, - /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ - "outDir": "dist", - /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "sourceMap": true, + /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + "outDir": "dist", + /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, - /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, - /* Ensure that casing is correct in imports. */ + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, + /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, + /* Ensure that casing is correct in imports. */ - /* Type Checking */ - "strict": true, - /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ - // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ - // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ - "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Type Checking */ + "strict": true, + /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + "alwaysStrict": true /* Ensure 'use strict' is always emitted. */, + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } -} \ No newline at end of file + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}