Project Description
Build an DDNS client using Object Oriented Programming and Async code that:
- Checks the status of a web page.
- If it’s down check that the DNS record matches the external IP of the server.
- If not then update the DNS Provider with an HTTP Get request.
- User can add more than one site.
- The program will loop through each site on an interval set by the user.
- The client will be able to make updates to freedns.afraid.org
Project Dependencies
Linux or Windows system running on the same web server, or network that share the external IP of the web server. Since node runs on either, the client will help keep your up-time for either kind of server.
Node.js The app will be written entirely in node. A back-end language built on Chrome’s V8 engine. version: 12.16.3 LTS.
Typescript (optional) If you opt-out of TypeScript, the assumption is that you know enough Node.js that you can figure out how/what to write based on the typescript. version 6.14.5
Planning
First we will list the separate responsibilities and use this plan to maintain Single Responsibility Principle
- Interval Polling of site analysis.
- Configuration of website information.
- Checking site availability.
- Checking DNS.
- Updating DNS using HTTP Request.
- Logging of Errors.
Initialization
Create a folder for the project. I will use C:/git/ddns-client
but you can use anything you like.
Run the following commands in the terminal.
PS C:\git\ddns-client> npm init
We’ll start with an npm init to generate our package.json. If this is your first time, accept defaults. You’ll learn the rest along the way. Add a start script below. the package.json file contains the meta information about your project, the dependencies, and the start, build or test scripts that you define.
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node dist/index.js"
},
PS C:\git\ddns-client> tsc --init
We’ll then use tsc init to generate our tsconfig.json. we’ll make the following changes to the tsconfig.json file.
"target": "ES2017",
"outDir": "./dist",
"resolveJsonModule": true,
//"strict": true, /* We'll take advantage of typescript, but not to this extreme */
TypeScript doesn’t ship with the knowledge of every possible import or language. This is on purpose to prevent projects from becoming too large with unused imports. run npm install @types/node -save-dev
.
PS C:\git\ddns-client> npm install @types/node -save-dev
The -save-dev
adds the dependency to the development dependency portion of the package.json.
How to transpile and run
Run tsc
to transpile the TypeScript code into JavaScript and then run the app by running npm start
.
PS C:\git\ddns-client> tsc
PS C:\git\ddns-client> npm start
Alternatively, open two terminals and run tsc -watch
to automatically transpile every time you save. In another terminal run npm start
when needed to start the app.
Terminal 1:
PS C:\git\ddns-client> tsc -watch
Terminal 2:
PS C:\git\ddns-client> npm start
The Code
Create a fairly empty index.ts file. If you like, add a console.log(“Hello World”) to validate your setup.
We’ll next need to create our classes that will be used throughout the app.
export class Site { public Hostname: string; public TestURL: string; public Endpoint: string; public SleepUntil?: Date; public LastLog?: Log; public LastUpdate?: Log; constructor(initializer?: Site){ Object.assign(this, initializer); } Sleep?(hours: number){ let sleepUntil = new Date(); sleepUntil.setHours(sleepUntil.getHours() + hours) this.SleepUntil = sleepUntil } IsSleep?(){ return this.SleepUntil && new Date() < this.SleepUntil}; } export class Log { public CreatedOn: Date; public Severity: Severity; public Message: string; public Details: string; constructor(severity: Severity, message: string, details: string = ''){ this.CreatedOn = new Date(); this.Severity = severity; this.Message = message; this.Details = details; console.log(this); } } export enum Severity { Info, Update, Error }Take note of how the Object.assign(this, initializer) is used in the constructor. For more explanation see Object.assign with this (Javascript). This will later allow us to map the JSON sites into our Site classes with minimal code.
Make an HTTP class. If you use any of the imports out there, find one that uses Promises. We’ll need that in our Async code later. Please examine the following code and comments.
import httpsDependancy from ‘https’; import httpDependancy from ‘http’; export class HttpWe’ll also need something to check if the DNS is working okay. Not going to re-invent the wheel here. This is basic DNS lookup wrapped in a promise.
import dns from “dns”; export function DNSLookup(targetSite){ let dnsServers = [“8.8.8.8″,”8.8.4.4”] return new PromiseBefore we can dive in, you need a working site. Define this information in a sites.json file. Modify the Hostname TestURL and Endpoint (username, password and hostname param) properties for your site.
Next we’re going to work on the logic piece. Examine the following code and comments.
import { DNSLookup } from “./Utilities”; import { Site, Severity, Log } from “./Models”; import sites from “./sites.json”; import { Http } from “./Http”; export class Analysis { IntervalJob; Sites: Site[]; CheckInterval: number = 5; IPService: string = “https://domains.google.com/checkip”; BeginInterval() { //Check the list of sites and then again every 5 minutes. this.AnalyzeSites(); this.IntervalJob = setInterval(() => { this.AnalyzeSites(); }, this.CheckInterval * 60 * 1000); return this; } AnalyzeSites(){ //If the sites is null then map the site.json into the Site array. if (!this.Sites) this.Sites = sites.map(site => new Site(site)) // Loop through each site calling the Analysis method. this.Sites.forEach(dnsEntry => { this.Analysis(dnsEntry) }); } async Analysis(site: Site) { // Check if the site responds without errors and bail the method if successful var IsSiteUp = await new HttpNo we’ll update the index.ts to start the application.
You can now run the app with npm start and run this through boot scripts or task scheduler.
Please comment below what should be added next to this project, Here are some things that can be added:
- Save Error logs to JSON file.
- Move configurable variables to config.json
- REST endpoint to see status of sites with Express
- Ability to sent HTTP Post requests.
- Separate sites from DNS Provider.
Complete Code
Github Repository: https://github.com/rimendoz86/ddns-slim