From be4d51a702a834bd6ed57b0752094edd719bb08e Mon Sep 17 00:00:00 2001 From: Steffen Date: Mon, 12 Aug 2019 22:08:00 +0200 Subject: [PATCH 01/70] :recycle: Use 'node-cron' Instead of 'cron' --- index.js | 8 +++----- package.json | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 39ca497..26f14a0 100755 --- a/index.js +++ b/index.js @@ -1,12 +1,10 @@ 'use strict'; const cloudflare = require('cloudflare'); -const cron = require('cron'); +const cron = require('node-cron'); require('colors'); const ctConverter = require('./lib/crontime-converter'); const ipUtil = require('./lib/ip-utils') -const CronJob = cron.CronJob; - let cf = null; let configEmail = null; let configKey = null; @@ -190,7 +188,7 @@ async function updateIpOfRecord(recordId, ip) { } function createCronJob(ddnsSync, cronTime, ip, callback) { - return new CronJob(cronTime, async () => { + return cron.schedule(cronTime, async () => { if(ip === null){ ip = await ipUtil.getIp(); } @@ -201,7 +199,7 @@ function createCronJob(ddnsSync, cronTime, ip, callback) { if (callbackGiven) { callback(syncResult); } - }, null, true); + }); } CloudflareDDNSSync.prototype.getIp = ipUtil.getIp; diff --git a/package.json b/package.json index 95e16f9..3894c21 100755 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "dependencies": { "cloudflare": "^2.4.1", "colors": "^1.2.1", - "cron": "^1.3.0", + "node-cron": "^2.0.3", "public-ip": "^2.4.0", "what-is-my-ip-address": "^1.0.3" }, From 94670d0c38d42e98c3054d3ee7ee908218f1d0e0 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 20:50:36 +0200 Subject: [PATCH 02/70] :sparkles: Add Issue Templates --- .github/ISSUE_TEMPLATE/--feature-request.md | 20 +++++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/--feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/--feature-request.md b/.github/ISSUE_TEMPLATE/--feature-request.md new file mode 100644 index 0000000..af595bd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/--feature-request.md @@ -0,0 +1,20 @@ +--- +name: "✨ Feature request" +about: Suggest an idea for this project +title: "✨ Short Summary of the Feature" +labels: feature-request +assignees: SteffenKn + +--- + +## Description + +> A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +## Possible Solution + +> A clear and concise description of what you want to happen. + +# Additional Context + +> Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..134664b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: 🐛 Bug report +about: Create a report to help us improve +title: "🐛 Short Summary of the Bug" +labels: bug +assignees: SteffenKn + +--- + +## Description + +> A clear and concise description of what the bug is. + +## Reproduction + +> Steps to reproduce the behavior: +> 1. Go to '...' +> 2. Click on '....' +> 3. Scroll down to '....' +> 4. See error + +## Expected Behavior + +> A clear and concise description of what you expected to happen. + +## Screenshots + +> If applicable, add screenshots (or even gifs) to help explain your problem. + +## Setup + +- OS: [`Windows`/`macOS`/`Linux` + `version`] +- Node: [`node --version`] +- Browser [`chrome`/`firefox`/`...` + version] + +## Additional context + +> Add any other context about the problem here. From 06a2530e61addab7aa12a80109c86cf7eff32993 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 20:55:43 +0200 Subject: [PATCH 03/70] :sparkles: Add Pull Request Template --- .github/PULL_REQUEST_TEMPLATE.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7ffcdf2 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ +## Changes + +1. Change 1 +2. Change 2 +3. (...) + +## Issues + +Closes #YourIssueNumber + +PR: #NumberOfThisPR + +## Checks + +[ ] The code is covered by tests. +[ ] The code is ready to get merged. From 65f24dda848bb55999611b6123beef4a2e11908f Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 5 Sep 2019 23:35:04 +0200 Subject: [PATCH 04/70] :fire: Remove No Longer Needed Eslintrc --- .eslintrc | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 58669eb..0000000 --- a/.eslintrc +++ /dev/null @@ -1,37 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 2017 - }, - "extends": "eslint:recommended", - "rules": { - "for-direction": 2, - "getter-return": 1, - "no-await-in-loop": 2, - "no-template-curly-in-string": 2, - "consistent-return": 1, - "default-case": 1, - "eqeqeq": 1, - "no-alert": 1, - "no-else-return": 2, - "no-empty-function": 2, - "no-eq-null": 1, - "no-extra-label": 1, - "no-fallthrough": 2, - "no-lone-blocks": 1, - "no-multi-spaces": 1, - "no-new": 2, - "no-return-assign": 2, - "no-return-await": 2, - "no-self-compare": 2, - "no-useless-concat": 1, - "no-useless-return": 1, - "no-void": 1, - "no-undef": 0, - "no-undef-init": 1, - "global-require": 1, - "array-bracket-spacing": 1, - "brace-style": 2, - "camelcase": 1, - "comma-dangle": 1 - } -} \ No newline at end of file From a9a35b401fce75e95ace91d9ecf37446aa51d3f9 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 5 Sep 2019 23:36:33 +0200 Subject: [PATCH 05/70] :recycle: Adjust 'package.json' --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 3894c21..0ee0a23 100755 --- a/package.json +++ b/package.json @@ -22,15 +22,15 @@ "test-convert-crontime": "mocha tests/test-convert-crontime.js" }, "dependencies": { - "cloudflare": "^2.4.1", - "colors": "^1.2.1", - "node-cron": "^2.0.3", - "public-ip": "^2.4.0", - "what-is-my-ip-address": "^1.0.3" + "cloudflare": "2.5.1", + "node-cron": "2.0.3", + "public-ip": "3.2.0", + "what-is-my-ip-address": "1.0.3" }, "devDependencies": { - "chai": "^4.1.2", - "eslint": "^4.19.1", - "mocha": "^5.0.4" + "@types/node": "10.14.17", + "chai": "4.2.0", + "mocha": "6.2.0", + "typescript": "3.6.2" } } From f4d7b202b800114a37bc358f910ddba590c10ffc Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 5 Sep 2019 23:36:51 +0200 Subject: [PATCH 06/70] :sparkles: Add 'tsconfig.json' --- tsconfig.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tsconfig.json diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8dc3ae5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "target": "esnext", + "module": "commonjs", + "rootDir": "src", + "outDir": "dist", + "alwaysStrict": true, + "declaration": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": false, + "removeComments": true, + "resolveJsonModule": true, + "strict": true, + "strictNullChecks": false + } +} From 261b4ec8fa6ee6f858cc8b1d4d2ae3a1a9a7e78b Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 5 Sep 2019 23:37:03 +0200 Subject: [PATCH 07/70] :sparkles: Add 'tslint.json' --- package.json | 2 + tslint.json | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 tslint.json diff --git a/package.json b/package.json index 0ee0a23..c5d3616 100755 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "url": "git://github.com:Steffen982/cloudflare-ddns-sync.git" }, "scripts": { + "lint": "tslint --project .", "test": "npm run test-convert-crontime && npm run test-sync", "test-sync": "mocha tests/test-sync.js --timeout 15000 --exit", "test-convert-crontime": "mocha tests/test-convert-crontime.js" @@ -31,6 +32,7 @@ "@types/node": "10.14.17", "chai": "4.2.0", "mocha": "6.2.0", + "tslint": "5.20.0", "typescript": "3.6.2" } } diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..b857b3d --- /dev/null +++ b/tslint.json @@ -0,0 +1,156 @@ +{ + "rules": { + "adjacent-overload-signatures": true, + "member-access": [true, "check-accessor"], + "member-ordering": [ + true, { + "order": [ + "static-field", + "instance-field", + "constructor", + "static-method", + "instance-method" + ] + } + ], + "no-any": true, + "no-empty-interface": true, + "no-inferrable-types": false, + "no-internal-module": true, + "no-magic-numbers": false, + "no-namespace": true, + "no-reference": true, + "no-var-requires": true, + "only-arrow-functions": [true, "allow-named-functions"], + "prefer-for-of": true, + "promise-function-async": false, + "typedef": [ + true, + "call-signature", + "arrow-call-signature", + "parameter", + "arrow-parameter", + "property-declaration", + "variable-declaration", + "member-variable-declaration" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ], + "unified-signatures": true, + "await-promise": false, + "curly": true, + "forin": false, + "import-blacklist": [true, "rxjs", "lodash"], + "label-position": true, + "no-arg": true, + "no-bitwise": false, + "no-conditional-assignment": true, + "no-console": [true, "log", "error"], + "no-construct": true, + "no-debugger": true, + "no-duplicate-variable": true, + "no-empty": true, + "no-eval": true, + "no-invalid-this": true, + "no-misused-new": true, + "no-null-keyword": false, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": false, + "no-this-assignment": true, + "no-unsafe-finally": false, + "no-unused-expression": [true, "allow-fast-null-checks"], + "no-use-before-declare": true, + "no-var-keyword": true, + "radix": false, + "strict-boolean-expressions": false, + "strict-type-predicates": false, + "switch-default": true, + "triple-equals": true, + "typeof-compare": true, + "use-isnan": true, + + "cyclomatic-complexity": [true, 20], + "eofline": true, + "indent": [true, "spaces", 2], + "linebreak-style": [true, "LF"], + "max-classes-per-file": [true, 1], + "max-file-line-count": [true, 3000], + "max-line-length": [true, 150], + "no-default-export": true, + "no-mergeable-namespace": true, + "no-require-imports": true, + "no-trailing-whitespace": true, + "object-literal-sort-keys": false, + "prefer-const": true, + "trailing-comma": [true, {"multiline": "always", "singleline": "never"}], + "align": [true, "parameters", "statements"], + "array-type": [true, "generic"], + "arrow-parens": true, + "arrow-return-shorthand": [false], + "callable-types": false, + "class-name": true, + "comment-format": [true, "check-space"], + "completed-docs": [false], + "file-header": [false], + "import-spacing": true, + "interface-name": [false], + "interface-over-type-literal": false, + "jsdoc-format": false, + "new-parens": true, + "no-angle-bracket-type-assertion": false, + "no-consecutive-blank-lines": [true, 1], + "no-parameter-properties": false, + "no-unnecessary-initializer": true, + "no-unused-variable": false, + "object-literal-key-quotes": [true, "as-needed"], + "object-literal-shorthand": false, + "one-line": [ + true, + "check-catch", + "check-finally", + "check-else", + "check-open-brace", + "check-whitespace" + ], + "one-variable-per-declaration": [true, "ignore-for-loop"], + "ordered-imports": true, + "prefer-function-over-method": false, + "prefer-method-signature": true, + "quotemark": [true, "single"], + "semicolon": [true, "always"], + "space-before-function-paren": [true, "never"], + "variable-name": [ + true, + "ban-keywords", + "check-format", + "allow-leading-underscore" + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast", + "check-preblock" + ] + } +} \ No newline at end of file From 79d664e6e5bf54149a4544b3fce2c443ba9b1a37 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 5 Sep 2019 23:37:19 +0200 Subject: [PATCH 08/70] :see_no_evil: Add 'dist' to '.gitignore' --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 504afef..478e5f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules/ +dist/ + package-lock.json From f2b865a8c7e56dabbb980f18482f48e20c276baa Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 5 Sep 2019 23:37:58 +0200 Subject: [PATCH 09/70] :fire: Remove No Longer Needed Crontime Converter --- lib/crontime-converter.js | 172 -------------------------------------- 1 file changed, 172 deletions(-) delete mode 100644 lib/crontime-converter.js diff --git a/lib/crontime-converter.js b/lib/crontime-converter.js deleted file mode 100644 index f8cebee..0000000 --- a/lib/crontime-converter.js +++ /dev/null @@ -1,172 +0,0 @@ -function getSecond(second){ - const secondFormatIsInvalid = second !== "*" - && (second < 0 - || second > 59) - - if (secondFormatIsInvalid){ - throw new Error(`Second must be between 0 and 59 or * (not ${second})`); - } - - return second; -} - -function getMinute(minute){ - const minuteFormatIsInvalid = minute !== "*" - && (minute < 0 - || minute > 59) - - if (minuteFormatIsInvalid){ - throw new Error(`Minute must be between 0 and 59 or * (not ${minute})`); - } - - return minute; -} - -function getHour(hour, minute){ - const hourFormatIsInvalid = hour !== "*" - && (hour < 0 - || hour > 24); - - if (hourFormatIsInvalid){ - throw new Error(`Hour must be between 0 and 24 (not ${hour})`); - } - - const minuteIsTooBig = hour === 24 - && minute > 0; - - if (minuteIsTooBig){ - throw new Error('Minute must be 0 when hour is 24'); - } - - return hour; -} - -function getDayOfMonth(dayOfMonth){ - const dayOfMonthIsInvalid = dayOfMonth !== "*" - && (dayOfMonth < 1 - || dayOfMonth > 31); - - if (dayOfMonthIsInvalid) { - throw new Error(`Day of month must be between 1 and 31 (not ${dayOfMonth})`); - } - - return dayOfMonth; -} - -function getMonth(month){ - const monthIsInvalid = month !== "*" - && (month < 0 - || month > 12); - - if (monthIsInvalid){ - throw new Error(`Month must be between 1 and 12 (not ${month})`); - } - - return month; -} - -function getDayOfWeek(dayOfWeek){ - const dayOfWeekIsInvalid = dayOfWeek !== "*" - && (dayOfWeek < 0 - || dayOfWeek > 7); - - if (dayOfWeekIsInvalid){ - throw new Error(`Day of week must be between 0 and 7 (not ${dayOfWeek})`); - } - - // This is used, because the 7. day of the week is actually the 0. for cron - const dayOfWeekIsSeven = dayOfWeek === 7; - if (dayOfWeekIsSeven) { - dayOfWeek = 0; - } - - return dayOfWeek; -} - -// interval = [second, minute, hour, dayOfMonth, month, dayOfWeek] -function convertIntervalToCronTime(interval) { - const intervalIsEmpty = interval.length === 0; - const intervalIsTooBig = interval.length > 6; - - if (intervalIsEmpty){ - throw new Error(`The interval can not be empty`); - } else if (intervalIsTooBig) { - throw new Error('The interval can not contain more than 6 ranges'); - } - - const timeArray = fillInterval(interval); - - try { - timeArray[0] = getSecond(timeArray[0]); - timeArray[1] = getMinute(timeArray[1]); - timeArray[2] = getHour(timeArray[2], timeArray[1]); - timeArray[3] = getDayOfMonth(timeArray[3]); - timeArray[4] = getMonth(timeArray[4]); - timeArray[5] = getDayOfWeek(timeArray[5]); - } catch (error){ - throw error; - } - - const cronTime = `${timeArray[0]} ${timeArray[1]} ${timeArray[2]} ${timeArray[3]} ${timeArray[4]} ${timeArray[5]}`; - return cronTime; -} - -function fillInterval(interval) { - let noSpecificValueSet = true; - - for (let i = 0; i < 6; i++){ - const intervalEntryIsNotSet = interval[i] === undefined; - const specificValueSet = interval[i] !== "*"; - - if (intervalEntryIsNotSet) { - interval[i] = "*"; - } else if (specificValueSet) { - noSpecificValueSet = false; - } - } - - if(noSpecificValueSet){ - throw new Error('The interval must contain some value except "*"'); - } - - return interval; -} - -function convertTimestringToCronTime(time){ - let [hour, minute] = [0, 0]; - - try { - [, hour, minute]= /(\d{2}):(\d{2})/.exec(time); - } catch (error){ - throw new Error('The interval must have this format: "24:00"'); - } - - hour = parseInt(hour); - minute = parseInt(minute); - - const timeIsNotValid = !checkTime(hour, minute); - if (timeIsNotValid) { - throw new Error('The timestring must be a time between "0:00" and "24:00"'); - } - - const cronTime = `0 ${minute} ${hour} * * *`; - return cronTime; -} - -function checkTime(hour, minute){ - const hourIsValid = hour >= 0 - && (hour < 24 - || (hour === 24 - && minute === 0)); - - const minuteIsValid = minute >= 0 - && minute < 60; - - const timeIsValid = hourIsValid && minuteIsValid; - return timeIsValid; -} - -module.exports = { - convertTimestringToCronTime, - convertIntervalToCronTime -}; From 42307e34cf29d5d23745beccbeca494fae918b37 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 5 Sep 2019 23:38:33 +0200 Subject: [PATCH 10/70] :recycle: Rewrite IP-Utils in TS --- lib/ip-utils.js | 48 --------------------------------------- src/lib/ip-utils.ts | 55 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 48 deletions(-) delete mode 100644 lib/ip-utils.js create mode 100644 src/lib/ip-utils.ts diff --git a/lib/ip-utils.js b/lib/ip-utils.js deleted file mode 100644 index 39a3f93..0000000 --- a/lib/ip-utils.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; -const wimIp = require('what-is-my-ip-address'); -const publicIp = require('public-ip'); - -let ipChangeInterval = null; - -async function getIp() { - try { - return await publicIp.v4() - } catch (error) { - return await wimIp.v4(); - } -} - -function onIpChange(ddnsSync, callback) { - const ipChangeIntervalIsExisting = ipChangeInterval !== null; - if (ipChangeIntervalIsExisting) { - console.error('Cloudflare-DDNS-Sync is already syncing on ip change. You do not need to call this multiple times.'); // eslint-disable-line - - return; - } - - ipChangeInterval = setInterval(async () => { - const currentIp = await getIp(); - const recordIps = await ddnsSync.getRecordIps(); - - for (const recordIp of recordIps) { - const recordIpMustBeUpdated = currentIp !== recordIp; - if (recordIpMustBeUpdated) { - callback(currentIp); - - break; - } - } - }, 10000); -} - -function stopOnIpChange() { - clearInterval(ipChangeInterval); - - ipChangeInterval = null; -} - -module.exports = { - getIp, - onIpChange, - stopOnIpChange -} diff --git a/src/lib/ip-utils.ts b/src/lib/ip-utils.ts new file mode 100644 index 0000000..b781969 --- /dev/null +++ b/src/lib/ip-utils.ts @@ -0,0 +1,55 @@ +import wimIp from 'what-is-my-ip-address'; +import publicIp from 'public-ip'; + +export default class IPUtils { + private static readonly ipPollingDelay: number = 10 * 1000; + + private static ipChangeEventListeners: Map = new Map(); + + public static async getIp(): Promise { + try { + return await publicIp.v4() + } catch (error) { + return wimIp.v4(); + } + } + + public static async addIpChangeListener(callback: Function): Promise { + const eventListenerId: string = this.getRandomId(); + + let previousIp: string = await this.getIp(); + + const intervalId: NodeJS.Timeout = setInterval(async () => { + const currentIp: string = await this.getIp(); + + const ipMustBeUpdated: boolean = currentIp !== previousIp; + if (ipMustBeUpdated) { + previousIp = currentIp; + + callback(currentIp); + } + }, this.ipPollingDelay); + + this.ipChangeEventListeners.set(eventListenerId, intervalId); + + return eventListenerId; + } + + public static removeIpChangeListener(eventListenerId: string): void { + const eventListenerIntervalId: NodeJS.Timeout = this.ipChangeEventListeners.get(eventListenerId); + + clearInterval(eventListenerIntervalId); + + IPUtils.ipChangeEventListeners.delete(eventListenerId); + } + + private static getRandomId(): string { + const beginningRandomString: string = Math.random().toString(36).substr(2); + const currentDateAsString: string = new Date().valueOf().toString(36); + const endingRandomString: string = Math.random().toString(36).substr(2); + + const randomString: string = beginningRandomString + currentDateAsString + endingRandomString; + + return randomString; + } +} From 2c587ce76eb0c2f2ff3156539d5d217d05beaaf1 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 5 Sep 2019 23:39:33 +0200 Subject: [PATCH 11/70] :sparkles: Add Build Script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index c5d3616..0f63f46 100755 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "url": "git://github.com:Steffen982/cloudflare-ddns-sync.git" }, "scripts": { + "build": "tsc", "lint": "tslint --project .", "test": "npm run test-convert-crontime && npm run test-sync", "test-sync": "mocha tests/test-sync.js --timeout 15000 --exit", From ac534f882b2fa71ce62af93080415bdeb088a4b0 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 11 Sep 2019 20:43:52 +0200 Subject: [PATCH 12/70] :fire: Remove No Longer Needed 'index.js' --- index.js | 439 ------------------------------------------------------- 1 file changed, 439 deletions(-) delete mode 100755 index.js diff --git a/index.js b/index.js deleted file mode 100755 index 26f14a0..0000000 --- a/index.js +++ /dev/null @@ -1,439 +0,0 @@ -'use strict'; -const cloudflare = require('cloudflare'); -const cron = require('node-cron'); -require('colors'); -const ctConverter = require('./lib/crontime-converter'); -const ipUtil = require('./lib/ip-utils') - -let cf = null; -let configEmail = null; -let configKey = null; -let configDomain = null; -let configRecords = []; - -let cfRecords = []; - -let cfZoneId = null; -let cfRecordIds = []; - -let initialized = false; - -const CloudflareDDNSSync = function(options) { - const cdsWasUsedWrong = !options - || !options.auth - || !options.domain - || !options.records - || !options.auth.email - || !options.auth.key - || !(options.records.length > 0); - - if (cdsWasUsedWrong) { - - throw new Error(`You used CloudflareDDNSSync wrong - Usage: CloudflareDDNSSync({ - "auth" : { - "email" : "your@email.com", - "key" : "your_cloudflare_api_key" - }, - "domain": "your-domain.com", - "records" : [ - "subdomain.your-domain.com", - "subdomain2.your-domain.com" - ], - });` - ); - } - - configEmail = options.auth.email; - configKey = options.auth.key; - configDomain = options.domain; - configRecords = options.records; - - cf = cloudflare({ - email: configEmail, - key: configKey - }); - - cfZoneId = getZoneId(); - cfRecordIds = getRecordIds(); -} - -async function initialSetup() { - cfRecordIds = await cfRecordIds; - - const newRecords = []; - - for (const configRecord of configRecords) { - const record = cfRecords.find((cfRecord) => { - return cfRecord.name === configRecord; - }); - - if (record === undefined) { - newRecords.push(addDnsRecord(configRecord)); - } - } - - return Promise.all(newRecords) - .then (() => { - cfRecordIds = getRecordIds(); - initialized = true; - }); -} - -function addDnsRecord(newRecordName) { - const newDnsRecord = { - name: newRecordName, - type: 'A', - content: '0.0.0.0' - } - - return cf.dnsRecords.add(cfZoneId, newDnsRecord); -} - -function getZoneId() { - return cf.zones - .browse() - .then((response) => { - const zones = response.result; - - for (const zone of zones) { - const searchedZoneFound = zone.name === configDomain; - if (searchedZoneFound) { - return zone.id; - } - } - - return null; - }); -} - -async function getRecordIds() { - cfZoneId = await cfZoneId; - cfRecords = await getRecords(); - - const recordIds = []; - - for (const record of cfRecords) { - const searchedRecordFound = configRecords.includes(record.name); - if (searchedRecordFound) { - recordIds.push(record.id); - } - - const allSearchedRecordsFound = recordIds.length === configRecords.length - if (allSearchedRecordsFound) { - break; - } - } - - return recordIds; -} - -function getRecords() { - return cf.dnsRecords - .browse(cfZoneId) - .then ((response) => { - const records = response.result; - - return records; - }); -} - -async function getRecordIps() { - const recordIps = []; - - for (const recordId of cfRecordIds) { - const currentIp = cf.dnsRecords - .read(cfZoneId, recordId) - .then((response) => { - return response.result.content; - }); - - recordIps.push(currentIp); - } - - return Promise.all(recordIps); -} - -function getRecord(recordId) { - return cf.dnsRecords - .read(cfZoneId, recordId) - .then((response) => { - return response.result; - }) - .catch((error) => { - return error.message; - }); -} - -function setRecord(recordId, record) { - return cf.dnsRecords.edit(cfZoneId, recordId, record) - .then((response) => { - const result = response.result; - const resultString = 'Successfully changed the IP of ' + `[${result.name}]`.yellow + ' to ' + `[${result.content}]`.green; - - return resultString; - }) - .catch((error) => { - return error.message; - }); -} - -async function updateIpOfRecord(recordId, ip) { - let record = await getRecord(recordId); - - record.content = ip; - - const result = await setRecord(recordId, record); - return result; -} - -function createCronJob(ddnsSync, cronTime, ip, callback) { - return cron.schedule(cronTime, async () => { - if(ip === null){ - ip = await ipUtil.getIp(); - } - - const syncResult = ddnsSync.sync(ip); - - const callbackGiven = typeof callback === 'function'; - if (callbackGiven) { - callback(syncResult); - } - }); -} - -CloudflareDDNSSync.prototype.getIp = ipUtil.getIp; - -CloudflareDDNSSync.prototype.getRecordIps = getRecordIps; - -CloudflareDDNSSync.prototype.sync = async function (ip) { - if (!initialized) { - await initialSetup(); - } - - const cfRecordsNotSet = cfRecordIds.then !== undefined; - if (cfRecordsNotSet) { - cfRecordIds = await cfRecordIds; - } - - let ipToSync = ip ? ip : await this.getIp(); - - const results = []; - - for (const recordId of cfRecordIds) { - const currentResult = updateIpOfRecord(recordId, ipToSync); - - results.push(currentResult); - } - - return Promise.all(results); -} - -CloudflareDDNSSync.prototype.syncOnIpChange = async function (callback) { - if (!initialized) { - await initialSetup(); - } - - const cfRecordsNotSet = cfRecordIds.then !== undefined; - if (cfRecordsNotSet) { - cfRecordIds = await cfRecordIds; - } - - ipUtil.onIpChange(this, (ip) => { - const result = this.sync(ip); - - const callbackIsSet = typeof callback === 'function'; - if (callbackIsSet) { - callback(result); - } - }); -} - -CloudflareDDNSSync.prototype.stopSyncOnIpChange = ipUtil.stopOnIpChange; - -CloudflareDDNSSync.prototype.syncByInterval = function (interval, ipOrCallback, callback) { - const intervalNotSet = interval === undefined; - - if (intervalNotSet) { - throw new Error('syncByInterval needs an interval'); - } - - const ipIsSet = typeof ipOrCallback !== 'function' - && ipOrCallback !== undefined; - - const ip = ipIsSet ? ipOrCallback : null; - - callback = ipIsSet ? callback - : ipOrCallback; - - try { - const cronTime = ctConverter.convertIntervalToCronTime(interval); - - return createCronJob(this, cronTime, ip, callback); - } catch (error) { - throw error; - } -} - -CloudflareDDNSSync.prototype.syncOnceEveryHour = function (minute, ipOrCallback, callback) { - const minuteNotSet = minute === undefined; - if (minuteNotSet) { - throw new Error('syncOnceEveryHour needs a minute'); - } - - const ipIsSet = typeof ipOrCallback !== 'function' - && ipOrCallback !== undefined; - - const ip = ipIsSet ? ipOrCallback : null; - - callback = ipIsSet ? callback - : ipOrCallback; - - try { - const interval = [0, minute]; - const cronTime = ctConverter.convertIntervalToCronTime(interval); - - return createCronJob(this, cronTime, ip, callback); - } catch (error) { - throw error; - } -} - -CloudflareDDNSSync.prototype.syncOnceEveryDay = function ([hour, minute], ipOrCallback, callback) { - const hourNotSet = hour === undefined; - if (hourNotSet) { - throw new Error('syncOnceEveryDay needs an interval'); - } - - const ipIsSet = typeof ipOrCallback !== 'function' - && ipOrCallback !== undefined; - - const ip = ipIsSet ? ipOrCallback : null; - - callback = ipIsSet ? callback - : ipOrCallback; - - try { - const interval = [0, minute||0, hour]; - const cronTime = ctConverter.convertIntervalToCronTime(interval); - - return createCronJob(this, cronTime, ip, callback); - } catch (error) { - throw error; - } -} - -CloudflareDDNSSync.prototype.syncOnceEveryWeek = function ([dayOfWeek, hour, minute], ipOrCallback, callback) { - const dayOfWeekNotSet = dayOfWeek === undefined; - if (dayOfWeekNotSet) { - throw new Error('syncOnceEveryWeek needs an interval'); - } - - const ipIsSet = typeof ipOrCallback !== 'function' - && ipOrCallback !== undefined; - - const ip = ipIsSet ? ipOrCallback : null; - - callback = ipIsSet ? callback - : ipOrCallback; - - - try { - const interval = [0, minute||0, hour||0, "*", "*", dayOfWeek]; - const cronTime = ctConverter.convertIntervalToCronTime(interval); - - return createCronJob(this, cronTime, ip, callback); - } catch (error) { - throw error; - } -} - -CloudflareDDNSSync.prototype.syncOnceEveryMonth = function ([dayOfMonth, hour, minute], ipOrCallback, callback) { - const dayOfMonthNotSet = dayOfMonth === undefined; - if (dayOfMonthNotSet) { - throw new Error('syncOnceEveryMonth needs an interval'); - } - - const ipIsSet = typeof ipOrCallback !== 'function' - && ipOrCallback !== undefined; - - const ip = ipIsSet ? ipOrCallback : null; - - callback = ipIsSet ? callback - : ipOrCallback; - - - try { - const interval = [0, minute||0, hour||0, dayOfMonth]; - const cronTime = ctConverter.convertIntervalToCronTime(interval); - - return createCronJob(this, cronTime, ip, callback); - } catch (error) { - throw error; - } -} - -CloudflareDDNSSync.prototype.syncByCronTime = function (cronTime, ipOrCallback, callback) { - const wrongTypeForCronTimeUsed = typeof cronTime !== 'string'; - if (wrongTypeForCronTimeUsed) { - throw new Error(`cronTime must be string not ${typeof cronTime}`); - } - - const ipIsSet = typeof ipOrCallback !== 'function' - && ipOrCallback !== undefined; - - const ip = ipIsSet ? ipOrCallback : null; - - callback = ipIsSet ? callback - : ipOrCallback; - - return createCronJob(this, cronTime, ip, callback); -} - -CloudflareDDNSSync.prototype.syncAtDate = function (date, ipOrCallback, callback) { - const invalidDateWasSet = date.toString() === 'Invalid Date'; - if (invalidDateWasSet) { - throw new Error('The date is invalid'); - } - - const invalidDateType = typeof date !== 'object'; - if (invalidDateType) { - throw new Error(`Date must not be ${typeof date}`); - } - - const dateInPast = Date.now() > date; - if (dateInPast) { - throw Error('The timetravel function is not working at the moment. The date must not be in the past') - } - - const ipIsSet = typeof ipOrCallback !== 'function' - && ipOrCallback !== undefined; - - const ip = ipIsSet ? ipOrCallback : null; - - callback = ipIsSet ? callback - : ipOrCallback; - - return createCronJob(this, date, ip, callback); -} - -CloudflareDDNSSync.prototype.syncByTimestring = function (timestring, ipOrCallback, callback) { - const ipIsSet = typeof ipOrCallback !== 'function' - && ipOrCallback !== undefined; - - const ip = ipIsSet ? ipOrCallback : null; - - callback = ipIsSet ? callback - : ipOrCallback; - - - try { - const cronTime = ctConverter.convertTimestringToCronTime(timestring); - - return createCronJob(this, cronTime, ip, callback); - } catch (error) { - throw error; - } -} - -module.exports = CloudflareDDNSSync; From 92d5b1d4aa5293b184fd4b336e7206c88749ad20 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 11 Sep 2019 20:47:29 +0200 Subject: [PATCH 13/70] :arrow_up: Update Cloudflare --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0f63f46..49fa9ba 100755 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "test-convert-crontime": "mocha tests/test-convert-crontime.js" }, "dependencies": { - "cloudflare": "2.5.1", + "cloudflare": "2.6.0", "node-cron": "2.0.3", "public-ip": "3.2.0", "what-is-my-ip-address": "1.0.3" From 08a882ae207b665d8688d71dc7d495a3922afc15 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 11 Sep 2019 20:48:53 +0200 Subject: [PATCH 14/70] :heavy_plus_sign: Add Types for 'node-cron' --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 49fa9ba..a07e1d3 100755 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "devDependencies": { "@types/node": "10.14.17", + "@types/node-cron": "2.0.2", "chai": "4.2.0", "mocha": "6.2.0", "tslint": "5.20.0", From cbb155ed166e73e7fb62846020a08f6ff994fe2a Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 11 Sep 2019 21:00:29 +0200 Subject: [PATCH 15/70] :sparkles: Add Cron Handler --- src/lib/cron.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/lib/cron.ts diff --git a/src/lib/cron.ts b/src/lib/cron.ts new file mode 100644 index 0000000..0037534 --- /dev/null +++ b/src/lib/cron.ts @@ -0,0 +1,18 @@ +import cron, {ScheduledTask} from 'node-cron'; + +export default class Cron { + public static createCronJob(cronExpression: string, callback: Function): ScheduledTask { + const cronExpressionIsInvalid: boolean = !this.isValid(cronExpression); + if(cronExpressionIsInvalid) { + throw new Error(`'${cronExpression}' is not a valid cron expression.\nHere you can see how cron expressions work: https://cds.knaup.pw/cron-expression`) + } + + return cron.schedule(cronExpression, () => { + callback(); + }, undefined); + } + + public static isValid(cronExpression: string): boolean { + return cron.validate(cronExpression); + } +} From 2322e2696491ef8c4045c0fb5717addbff9531b9 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 11 Sep 2019 21:03:24 +0200 Subject: [PATCH 16/70] :sparkles: Add Cloudflare Client --- src/contracts/IRecord.ts | 8 + src/contracts/ZoneMap.ts | 1 + src/contracts/cloudflare/Record.ts | 21 +++ src/contracts/cloudflare/Zone.ts | 43 ++++++ src/contracts/cloudflare/index.ts | 2 + src/contracts/index.ts | 4 + src/lib/cloudflare-client.ts | 240 +++++++++++++++++++++++++++++ 7 files changed, 319 insertions(+) create mode 100644 src/contracts/IRecord.ts create mode 100644 src/contracts/ZoneMap.ts create mode 100644 src/contracts/cloudflare/Record.ts create mode 100644 src/contracts/cloudflare/Zone.ts create mode 100644 src/contracts/cloudflare/index.ts create mode 100644 src/contracts/index.ts create mode 100644 src/lib/cloudflare-client.ts diff --git a/src/contracts/IRecord.ts b/src/contracts/IRecord.ts new file mode 100644 index 0000000..8d03420 --- /dev/null +++ b/src/contracts/IRecord.ts @@ -0,0 +1,8 @@ +export interface IRecord { + name: string, + type?: string, + proxied?: boolean, + ttl?: number, + priority?: number, + content?: string; +} diff --git a/src/contracts/ZoneMap.ts b/src/contracts/ZoneMap.ts new file mode 100644 index 0000000..48f8505 --- /dev/null +++ b/src/contracts/ZoneMap.ts @@ -0,0 +1 @@ +export type ZoneMap = Map; diff --git a/src/contracts/cloudflare/Record.ts b/src/contracts/cloudflare/Record.ts new file mode 100644 index 0000000..bbf3f6c --- /dev/null +++ b/src/contracts/cloudflare/Record.ts @@ -0,0 +1,21 @@ +export type Record = { + id: string; + type: string; + name: string; + content: string; + proxiable: boolean; + proxied: boolean; + ttl: number; + locked: boolean; + zone_id: string; + zone_name: string; + modified_on: string; + created_on: string; + meta: RecordMetaData; +} + +export type RecordMetaData = { + auto_added: boolean; + managed_by_apps: boolean; + managed_by_argo_tunnel: boolean; +} diff --git a/src/contracts/cloudflare/Zone.ts b/src/contracts/cloudflare/Zone.ts new file mode 100644 index 0000000..0eaa48d --- /dev/null +++ b/src/contracts/cloudflare/Zone.ts @@ -0,0 +1,43 @@ +export type Zone = { + id: string; + name: string; + status: string; + paused: boolean; + type: string; + development_mode: number; + name_servers: Array; + original_name_servers: Array; + modified_on: Date; + created_on: Date; + activated_on: Date; + meta: { + step: number, + wildcard_proxiable: boolean, + custom_certificate_quota: number, + page_rule_quota: number, + phishing_detected: boolean, + multiple_railguns_allowed: boolean + }; + owner: { + id: string, + type: string, + email: string, + }; + account: { + id: string, + name: string + }; + permissions: Array; + plan: { + id: string, + name: string, + price: number, + currency: string, + frequency: string, + is_subscribed: boolean, + can_subscribe: boolean, + legacy_id: string, + legacy_discount: boolean, + externally_managed: boolean + } +} diff --git a/src/contracts/cloudflare/index.ts b/src/contracts/cloudflare/index.ts new file mode 100644 index 0000000..ba8eabc --- /dev/null +++ b/src/contracts/cloudflare/index.ts @@ -0,0 +1,2 @@ +export * from './Record'; +export * from './Zone'; diff --git a/src/contracts/index.ts b/src/contracts/index.ts new file mode 100644 index 0000000..9f89d92 --- /dev/null +++ b/src/contracts/index.ts @@ -0,0 +1,4 @@ +export * from './cloudflare/index'; + +export * from './IRecord'; +export * from './ZoneMap'; diff --git a/src/lib/cloudflare-client.ts b/src/lib/cloudflare-client.ts new file mode 100644 index 0000000..daa146b --- /dev/null +++ b/src/lib/cloudflare-client.ts @@ -0,0 +1,240 @@ +import Cloudflare from 'cloudflare'; +import parseDomain from 'parse-domain'; + +import {IRecord, Record, Zone, ZoneMap} from "../contracts"; + +export default class CloudflareClient { + private cloudflare; + + private zoneMap: ZoneMap = new Map(); + + constructor(email: string, authKey: string) { + this.cloudflare = new Cloudflare({ + email: email, + key: authKey, + }); + + this.updateZoneMap(); + } + + public async syncRecord(record: IRecord, ip?: string): Promise { + const recordIds: Map = await this.getRecordIdsForRecords([record]); + + const zoneId: string = await this.getZoneIdByRecordName(record.name); + const recordId: string = recordIds.get(record.name); + + const recordExists: boolean = recordId !== undefined; + if(recordExists) { + const result = await this.updateRecord(zoneId, recordId, record, ip); + + return result; + } else { + const result = await this.createRecord(zoneId, record, ip); + + return result; + } + } + + public async syncRecords(records: Array, ip?: string): Promise> { + const recordIds: Map = await this.getRecordIdsForRecords(records); + + const resultPromises: Array> = records.map(async (record: IRecord) => { + const zoneId: string = await this.getZoneIdByRecordName(record.name); + const recordId: string = recordIds.get(record.name); + + const recordExists: boolean = recordId !== undefined; + if(recordExists) { + const currentResult = await this.updateRecord(zoneId, recordId, record, ip); + + return currentResult; + } else { + const currentResult = await this.createRecord(zoneId, record, ip); + + return currentResult; + } + + }); + + const results: Array = await Promise.all(resultPromises); + + return results; + } + + public async removeRecord(zoneId: string, recordId: string): Promise { + try { + await this.cloudflare.dnsRecords.del(zoneId, recordId); + } catch(error) { + throw error; + } + } + + public async getZoneIdByRecordName(recordName: string): Promise { + const domain: string = this.getDomainByRecordName(recordName); + + return this.getZoneIdByDomain(domain); + } + + public async getRecordIdByName(recordName: string): Promise { + const record: Record = await this.getRecordByName(recordName); + + return record.id; + } + + // TODO: Performance? + public async getRecordDataForRecord(record: IRecord): Promise { + console.time('getRecordDataForRecord'); + const domain: string = this.getDomainByRecordName(record.name); + + const recordDataForDomain: Array = await this.getRecordsByDomain(domain); + + const recordData: Record = recordDataForDomain.find((singleRecordData: Record) => { + return record.name === singleRecordData.name; + }); + + console.timeEnd('getRecordDataForRecord'); + return recordData; + } + + // TODO: Performance? + public async getRecordDataForRecords(records: Array): Promise> { + console.time('getRecordDataForRecords'); + const domains: Array = this.getDomainsFromRecords(records); + + const recordDataPromises: Array>> = domains.map(async (domain: string) => { + const recordDataForDomain: Array = await this.getRecordsByDomain(domain); + + const recordData: Array = recordDataForDomain.filter((singleRecordData: Record) => { + return records.some((record: IRecord) => { + return record.name === singleRecordData.name; + }); + }); + + return recordData; + }); + + const recordDataForDomains: Array> = await Promise.all(recordDataPromises); + const recordData: Array = [].concat(...recordDataForDomains); + + console.timeEnd('getRecordDataForRecords'); + return recordData; + } + + private async createRecord(zoneId: string, record: IRecord, ip?: string): Promise { + const copyOfRecord: IRecord = Object.assign({}, record); + copyOfRecord.content = copyOfRecord.content ? copyOfRecord.content : ip; + copyOfRecord.type = copyOfRecord.type ? copyOfRecord.type : 'A'; + + if(!copyOfRecord.content) { + throw Error(`Could not create Record "${record.name}": Ip is missing!`) + } + + try { + const response = await this.cloudflare.dnsRecords.add(zoneId, copyOfRecord); + + return response.result; + } catch(error) { + throw error; + } + } + + private async updateRecord(zoneId: string, recordId: string, record: IRecord, ip?: string): Promise { + const copyOfRecord: IRecord = Object.assign({}, record); + copyOfRecord.content = copyOfRecord.content ? copyOfRecord.content : ip; + copyOfRecord.type = copyOfRecord.type ? copyOfRecord.type : 'A'; + + if(!copyOfRecord.content) { + throw Error(`Could not update Record "${record.name}": Ip is missing!`) + } + + try { + const response = await this.cloudflare.dnsRecords.edit(zoneId, recordId, copyOfRecord); + + return response.result; + } catch(error) { + throw error; + } + } + + private async updateZoneMap(): Promise { + const response = await this.cloudflare.zones.browse(); + const zones: Array = response.result; + + this.zoneMap = new Map(); + for(const zone of zones) { + this.zoneMap.set(zone.name, zone.id); + } + } + + private async getRecordByName(recordName: string): Promise { + const domain: string = this.getDomainByRecordName(recordName); + + const records: Array = await this.getRecordsByDomain(domain); + + const record: Record = records.find((record: Record) => { + return record.name === recordName; + }); + + const recordNotFound: boolean = record === undefined; + if(recordNotFound) { + throw new Error(`Record '${recordName}' not found.`); + } + + return record; + } + + // TODO: Performance? + private async getRecordIdsForRecords(records: Array): Promise> { + console.time('getRecordIdsToUpdate'); + + const recordIdMap: Map = new Map(); + + const recordData: Array = await this.getRecordDataForRecords(records); + + for(const record of recordData) { + recordIdMap.set(record.name, record.id); + } + + console.timeEnd('getRecordIdsToUpdate'); + + return recordIdMap; + } + + private async getRecordsByDomain(domain: string): Promise> { + const zoneId: string = await this.getZoneIdByDomain(domain); + + const response = await this.cloudflare.dnsRecords.browse(zoneId); + const records: Array = response.result; + + return records; + } + + private async getZoneIdByDomain(domain: string): Promise { + if(this.zoneMap.has(domain)) { + const zoneId: string = this.zoneMap.get(domain); + + return zoneId; + } + + + await this.updateZoneMap(); + const zoneId: string = this.zoneMap.get(domain); + + return zoneId; + } + + private getDomainsFromRecords(records: Array): Array { + const domains: Array = records.map((record: IRecord) => { + return this.getDomainByRecordName(record.name); + }).filter((domain: string, index: number, domains: Array) => { + return domains.indexOf(domain) == index; + }); + + return domains; + } + + private getDomainByRecordName(recordName: string): string { + const parsedDomain = parseDomain(recordName); + + return `${parsedDomain.domain}.${parsedDomain.tld}`; + } +} \ No newline at end of file From db7b2d3bba25d8d3a755422d4a952a48f38a8a93 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 11 Sep 2019 21:20:20 +0200 Subject: [PATCH 17/70] :sparkles: Add Index File --- package.json | 1 + src/contracts/Callbacks.ts | 4 ++ src/contracts/SyncResult.ts | 4 ++ src/contracts/index.ts | 2 + src/index.ts | 85 +++++++++++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+) create mode 100644 src/contracts/Callbacks.ts create mode 100644 src/contracts/SyncResult.ts create mode 100644 src/index.ts diff --git a/package.json b/package.json index a07e1d3..649d165 100755 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dependencies": { "cloudflare": "2.6.0", "node-cron": "2.0.3", + "parse-domain": "2.3.2", "public-ip": "3.2.0", "what-is-my-ip-address": "1.0.3" }, diff --git a/src/contracts/Callbacks.ts b/src/contracts/Callbacks.ts new file mode 100644 index 0000000..98fd953 --- /dev/null +++ b/src/contracts/Callbacks.ts @@ -0,0 +1,4 @@ +import {Record} from './index'; + +export type SingleSyncCallback = (syncResult: Record) => void; +export type MultiSyncCallback = (syncResult: Array) => void; diff --git a/src/contracts/SyncResult.ts b/src/contracts/SyncResult.ts new file mode 100644 index 0000000..bef3edf --- /dev/null +++ b/src/contracts/SyncResult.ts @@ -0,0 +1,4 @@ +import {Record} from './index'; + +export type SingleSyncResult = Promise; +export type MultiSyncResult = Promise>; diff --git a/src/contracts/index.ts b/src/contracts/index.ts index 9f89d92..f72106b 100644 --- a/src/contracts/index.ts +++ b/src/contracts/index.ts @@ -1,4 +1,6 @@ export * from './cloudflare/index'; +export * from './Callbacks'; export * from './IRecord'; +export * from './SyncResult'; export * from './ZoneMap'; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e9e4f36 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,85 @@ +import {ScheduledTask} from 'node-cron'; + +import CloudflareClient from './lib/cloudflare-client'; +import Cron from './lib/cron'; +import ipUtils from './lib/ip-utils'; + +import { + IRecord, + Record, + MultiSyncCallback, + MultiSyncResult, + SingleSyncResult, +} from './contracts/index'; + +export default class CloudflareDDNSSync { + public cloudflareClient: CloudflareClient; + + constructor(email: string, authKey: string) { + this.cloudflareClient = new CloudflareClient(email, authKey); + } + + public getRecordDataForRecords(records: Array): Promise> { + return this.cloudflareClient.getRecordDataForRecords(records); + } + + public getIp(): Promise { + return ipUtils.getIp(); + } + + public async createRecord(record: IRecord): Promise { + return this.cloudflareClient.syncRecord(record); + } + + public async removeRecord(recordName: string): Promise { + const zoneId: string = await this.cloudflareClient.getZoneIdByRecordName(recordName); + const recordId: string = await this.cloudflareClient.getRecordIdByName(recordName); + + + this.cloudflareClient.removeRecord(zoneId, recordId); + } + + public async sync(record: IRecord, ip?: string): SingleSyncResult { + + const ipToUse: string = ip ? ip : await ipUtils.getIp(); + + return this.cloudflareClient.syncRecord(record, ipToUse); + } + + public async syncRecords(records: Array, ip?: string): MultiSyncResult { + const currentIp: string = await ipUtils.getIp(); + + const ipToUse: string = ip ? ip : currentIp; + + return this.cloudflareClient.syncRecords(records, ipToUse); + } + + public async syncOnIpChange(records: Array, callback: MultiSyncCallback): Promise { + const changeListenerId: string = await ipUtils.addIpChangeListener(async(ip: string) => { + const result = await this.syncRecords(records, ip); + + callback(result); + }); + + const currentIp: string = await ipUtils.getIp(); + this.syncRecords(records, currentIp).then((records: Array) => { + callback(records); + }); + + return changeListenerId + } + + public stopSyncOnIpChange(changeListenerId: string): void { + ipUtils.removeIpChangeListener(changeListenerId); + } + + public syncByCronTime(cronExpression: string, records: Array, callback: MultiSyncCallback, ip?: string): ScheduledTask { + return Cron.createCronJob(cronExpression, async() => { + const result: Array = await this.syncRecords(records, ip); + + callback(result); + }) + } +} + +module.exports = CloudflareDDNSSync; From 53d4dc612fe308623c4da6dd0c116636801295ad Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 11 Sep 2019 21:20:59 +0200 Subject: [PATCH 18/70] :fire: Remove Old Tests --- tests/auth.json | 10 --- tests/test-convert-crontime.js | 114 --------------------------------- tests/test-sync.js | 104 ------------------------------ 3 files changed, 228 deletions(-) delete mode 100644 tests/auth.json delete mode 100644 tests/test-convert-crontime.js delete mode 100644 tests/test-sync.js diff --git a/tests/auth.json b/tests/auth.json deleted file mode 100644 index 60d8111..0000000 --- a/tests/auth.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "auth" : { - "email" : "your@email.com", - "key" : "your_cloudflare_api_key" - }, - "domain": "your-domain.com", - "records" : [ - "subdomain.your-domain.com" - ] -} diff --git a/tests/test-convert-crontime.js b/tests/test-convert-crontime.js deleted file mode 100644 index d38e271..0000000 --- a/tests/test-convert-crontime.js +++ /dev/null @@ -1,114 +0,0 @@ -'use strict'; -const chai = require('chai'); -const expect = chai.expect; - -const ctConverter = require('../lib/crontime-converter'); - -describe ('Convert Interval to CronTime', () => { - describe ('ByInterval', () => { - it ('[0, 1, 2] should return "0 1 2 * * *"', () => { - const result = ctConverter.convertIntervalToCronTime([0, 1, 2]); - - expect(result).to.equal('0 1 2 * * *'); - }); - - it ('[] should throw an error', () => { - const convertIntervalFunction = function () { - ctConverter.convertIntervalToCronTime([]) - }; - - expect(convertIntervalFunction).to.throw('The interval can not be empty'); - }); - - it ('[0, 1, 2, 3, 4, 5, 6] should throw an error', () => { - const convertIntervalFunction = function () { - ctConverter.convertIntervalToCronTime([0, 1, 2, 3, 4, 5, 6]); - }; - - expect(convertIntervalFunction).to.throw('The interval can not contain more than 6 ranges'); - }); - - it ('["*", "*", "*", "*", "*", "*"] should throw an error', () => { - const convertIntervalFunction = function () { - ctConverter.convertIntervalToCronTime(["*", "*", "*", "*", "*", "*"]); - }; - expect(convertIntervalFunction).to.throw('The interval must contain some value except "*"'); - }); - }) - - - describe ('OnceEveryWeek', () => { - it ('[0, 0, 7, "*", "*", 3] should return "0 0 7 * * 3"', () => { - const result = ctConverter.convertIntervalToCronTime([0, 0, 7, "*", "*", 3]); - - expect(result).to.equal('0 0 7 * * 3'); - }); - - it ('[0, 0, 7, "*", "*", 7] should return "0 0 7 * * 0"', () => { - const result = ctConverter.convertIntervalToCronTime([0, 0, 7, "*", "*", 7]); - - expect(result).to.equal('0 0 7 * * 0'); - }); - - it ('[0, 0, 7, "*", "*", 8] should throw an error', () => { - const convertIntervalFunction = function () { - ctConverter.convertIntervalToCronTime([0, 0, 7, "*", "*", 8]); - }; - - expect(convertIntervalFunction).to.throw('Day of week must be between 0 and 7 (not 8)'); - }); - }); - - describe ('OnceEveryMonth', () => { - it ('[0, 0, 3, 5] should return "0 0 3 5 * *"', () => { - const result = ctConverter.convertIntervalToCronTime([0, 0, 3, 5]); - - expect(result).to.equal('0 0 3 5 * *'); - }); - }); -}); - - -describe('Convert Timestring to CronTime', () => { - it ('"13:12" should return "0 12 13 * * *"', () => { - const result = ctConverter.convertTimestringToCronTime("13:12"); - - expect(result).to.equal('0 12 13 * * *'); - }); - - it ('"23:59" should return "0 59 23 * * *"', () => { - const result = ctConverter.convertTimestringToCronTime("23:59"); - - expect(result).to.equal('0 59 23 * * *'); - }); - - it ('"24:00" should return "0 0 24 * * *"', () => { - const result = ctConverter.convertTimestringToCronTime("24:00"); - - expect(result).to.equal('0 0 24 * * *'); - }); - - it ('"24:01" should throw an error', () => { - const convertTimestampFunction = function () { - ctConverter.convertTimestringToCronTime("24:01"); - }; - - expect(convertTimestampFunction).to.throw('The timestring must be a time between "0:00" and "24:00"'); - }); - - it ('"12:60" should throw an error', () => { - const convertTimestampFunction = function () { - ctConverter.convertTimestringToCronTime("12:60"); - }; - - expect(convertTimestampFunction).to.throw('The timestring must be a time between "0:00" and "24:00"'); - }); - - it ('"-1:00" should throw an error', () => { - const convertTimestampFunction = function () { - ctConverter.convertTimestringToCronTime("-1:00"); - }; - - expect(convertTimestampFunction).to.throw('The interval must have this format: "24:00"'); - }); -}); diff --git a/tests/test-sync.js b/tests/test-sync.js deleted file mode 100644 index 3184f01..0000000 --- a/tests/test-sync.js +++ /dev/null @@ -1,104 +0,0 @@ -'use strict'; -const CloudflareDDNSSync = require("../index"); -const cloudflare = require('cloudflare'); -const auth = require('./auth'); -const chai = require('chai'); -const expect = chai.expect; - -const ddnsSync = new CloudflareDDNSSync(auth); -const cf = cloudflare({ - email: auth.auth.email, - key: auth.auth.key -}); -let zoneId = getZoneId(); -let recordId = getRecordId(); - -describe ('Sync Functionality', () => { - it ('sync should return success message', async () => { - const results = await ddnsSync.sync('1.2.3.4'); - - expect(results[0]).to.equal('Successfully changed the IP of ' + `[${auth.records[0]}]`.yellow + ' to ' + `[1.2.3.4]`.green); - }); - - it ('the ip of the dns record should be "1.2.3.4"', async () => { - const ddnsIp = await getRecordIps(); - - expect(ddnsIp).to.equal('1.2.3.4'); - }); - - it ('the ip of the dns record should become the external ip', async () => { - const ip = await ddnsSync.getIp(); - await ddnsSync.sync(); - const ddnsIp = await getRecordIps(); - - expect(ddnsIp).to.equal(ip); - }); - - it ('the ip of the dns record becomes updated when the dns record is different to the external ip', async () => { - const ddnsIp = await getRecordIps(); - const ip = await testSyncOnIpChange(); - - expect(ddnsIp).to.equal(ip); - }); -}); - -async function testSyncOnIpChange() { - return new Promise((resolve) => { - ddnsSync.syncOnIpChange(); - - setTimeout(async () => { - await ddnsSync.sync('1.2.3.4'); - - const ip = await ddnsSync.getIp(); - - setTimeout(async () => { - resolve(ip); - }, 2000); - - }, 2000); - }); -} - -async function getRecordIps() { - recordId = await recordId; - - return cf.dnsRecords.read(zoneId, recordId).then((response) => { - return response.result.content; - }); -} - -function getZoneId() { - return cf.zones - .browse() - .then((response) => { - const zones = response.result; - - const zone = zones.find((zone) => { - return zone.name === auth.domain; - }); - - if(zone !== undefined) { - return zone.id; - } - - return null; - }); -} - -async function getRecordId() { - zoneId = await zoneId; - - return cf.dnsRecords - .browse(zoneId) - .then((response) => { - const records = response.result; - - for(const record of records) { - if(auth.records.includes(record.name)) { - return record.id; - } - } - - return null; - }); -} From c3338913305f02b8c9afc630a7264fb1ddbd2426 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 11 Sep 2019 22:04:27 +0200 Subject: [PATCH 19/70] :white_check_mark: Add Tests for Cron Handler --- package.json | 6 ++-- src/tests/cron-tests.ts | 73 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/tests/cron-tests.ts diff --git a/package.json b/package.json index 649d165..d4eeeda 100755 --- a/package.json +++ b/package.json @@ -19,9 +19,7 @@ "scripts": { "build": "tsc", "lint": "tslint --project .", - "test": "npm run test-convert-crontime && npm run test-sync", - "test-sync": "mocha tests/test-sync.js --timeout 15000 --exit", - "test-convert-crontime": "mocha tests/test-convert-crontime.js" + "test": "mocha dist/tests/*.js --timeout 15000 --exit" }, "dependencies": { "cloudflare": "2.6.0", @@ -31,6 +29,8 @@ "what-is-my-ip-address": "1.0.3" }, "devDependencies": { + "@types/chai": "4.2.2", + "@types/mocha": "5.2.7", "@types/node": "10.14.17", "@types/node-cron": "2.0.2", "chai": "4.2.0", diff --git a/src/tests/cron-tests.ts b/src/tests/cron-tests.ts new file mode 100644 index 0000000..fa65c84 --- /dev/null +++ b/src/tests/cron-tests.ts @@ -0,0 +1,73 @@ +import chai from 'chai'; +import {ScheduledTask} from 'node-cron'; + +import cron from '../lib/cron'; + +const expect: Chai.ExpectStatic = chai.expect; + +describe('Cron Handler', () => { + + describe('Validate Cron Expressions', () => { + it('"* * * * *" should be valid"', () => { + const result: boolean = cron.isValid('* * * * *'); + + expect(result).to.be.true; + }); + + it('"1 2 3 4 5" should be valid"', () => { + const result: boolean = cron.isValid('1 2 3 4 5'); + + expect(result).to.be.true; + }); + + it('"* * * * * *" should be valid"', () => { + const result: boolean = cron.isValid('* * * * * *'); + + expect(result).to.be.true; + }); + + it('"1 2 3 4 5 6" should be valid"', () => { + const result: boolean = cron.isValid('1 2 3 4 5 6'); + + expect(result).to.be.true; + }); + + it('"test" should not be valid"', () => { + const result: boolean = cron.isValid('test'); + + expect(result).to.be.false; + }); + + it('"1 2 3 4 5 a" should not be valid"', () => { + const result: boolean = cron.isValid('1 2 3 4 5 a'); + + expect(result).to.be.false; + }); + }); + + describe('Schedule Cron Expressions', () => { + it('should schedule "*/1 * * * * *"', (done: Function) => { + try { + const scheduledTask: ScheduledTask = cron.createCronJob('*/1 * * * * *', () => { + done(); + + scheduledTask.destroy(); + }); + } catch (error) { + done(`Error scheduling "*/1 * * * * *": ${error}`); + } + }); + + it('should not schedule "*/2 * * * * a"', (done: Function) => { + try { + cron.createCronJob('*/2 * * * * a', () => { + // This should never be called + }); + + done(`Error: "*/2 * * * * a" was scheduled.`); + } catch (error) { + done(); + } + }); + }); +}); From 4b191b053c6e0a0f7ddfc4b787c785be0ffdc633 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 11 Sep 2019 22:12:42 +0200 Subject: [PATCH 20/70] :white_check_mark: Add Tests for IPUtils --- src/tests/ip-utils-test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/tests/ip-utils-test.ts diff --git a/src/tests/ip-utils-test.ts b/src/tests/ip-utils-test.ts new file mode 100644 index 0000000..a845416 --- /dev/null +++ b/src/tests/ip-utils-test.ts @@ -0,0 +1,17 @@ +import chai from 'chai'; + +import IpUtils from '../lib/ip-utils'; + +const expect = chai.expect; +const ipRegex: RegExp = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/; + +describe('IPUtils', () => { + describe('Get IP', () => { + it('should get a valid ip', async() => { + const ip: string = await IpUtils.getIp(); + + expect(ip).to.be.string; + expect(ip).to.match(ipRegex); + }); + }); +}); From 93b7b46b20c1d41bd0370714e1f40a6c2f8847cd Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 00:19:10 +0200 Subject: [PATCH 21/70] :white_check_mark: Add Tests for CloudflareClient --- src/tests/auth.json | 14 +++ src/tests/cloudflare-client-test.ts | 151 ++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 src/tests/auth.json create mode 100644 src/tests/cloudflare-client-test.ts diff --git a/src/tests/auth.json b/src/tests/auth.json new file mode 100644 index 0000000..28d486f --- /dev/null +++ b/src/tests/auth.json @@ -0,0 +1,14 @@ +{ + "auth" : { + "email" : "your@email.com", + "key" : "your_cloudflare_api_key" + }, + "records" : [ + { + "name": "cddnstest.your-domain.com" + }, + { + "name": "cddns.test.your-domain.com" + } + ] +} diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts new file mode 100644 index 0000000..c27a7ff --- /dev/null +++ b/src/tests/cloudflare-client-test.ts @@ -0,0 +1,151 @@ +import chai from 'chai'; + +import CloudflareClient from '../lib/cloudflare-client'; + +import * as authConfig from './auth.json'; +import {IRecord, Record} from '../contracts'; +import IPUtils from '../lib/ip-utils'; + +const expect = chai.expect; + +const cloudflareClient: CloudflareClient = new CloudflareClient(authConfig.auth.email, authConfig.auth.key) + +describe('Cloudflare Client', () => { + it('should get the zone id of a domain', async() => { + const record: IRecord = authConfig.records[0]; + const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); + + expect(zoneId).to.be.string; + expect(zoneId.length).to.be.greaterThan(0); + }); + + describe('Add, Get, Remove', () => { + it('should be able to create a record', async() => { + const record: IRecord = authConfig.records[0]; + + record.content = '1.2.3.4'; + + const createdRecord: Record = await cloudflareClient.syncRecord(record); + + const expectedRecordType: string = record.type ? record.type : 'A'; + expect(createdRecord.id).to.be.string; + expect(createdRecord.id.length).to.be.greaterThan(0); + expect(createdRecord.name).to.be.string; + expect(createdRecord.name.length).to.be.greaterThan(0); + expect(createdRecord.type).to.be.string; + expect(createdRecord.type).to.equal(expectedRecordType); + }); + + it('should be able to get a record id', async() => { + const record: IRecord = authConfig.records[0]; + const recordId: string = await cloudflareClient.getRecordIdByName(record.name); + + + expect(recordId).to.be.string; + expect(recordId.length).to.be.greaterThan(0); + }); + + it('should be able to remove a record', async() => { + const record: IRecord = authConfig.records[0]; + const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); + + const recordId: string = await cloudflareClient.getRecordIdByName(record.name); + + await cloudflareClient.removeRecord(zoneId, recordId); + }); + }); + + it('should get record data for records', async() => { + const records: Array = authConfig.records; + + for (const record of records) { + await cloudflareClient.syncRecord(record, '1.2.3.4'); + } + + const recordData: Array = await cloudflareClient.getRecordDataForRecords(records); + + const recordDataNames: Array = recordData.map((recordDataEntry: Record) => { + return recordDataEntry.name; + }) + + expect(recordData.length).to.equal(records.length); + expect(recordDataNames).to.contain(records[0].name); + + for (const record of recordData) { + const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); + + await cloudflareClient.removeRecord(zoneId, record.id); + } + }); + + it('should sync existing record', async() => { + const record: IRecord = authConfig.records[0]; + + await cloudflareClient.syncRecord(record); + const recordData: Record = await cloudflareClient.syncRecord(record); + + expect(recordData.name).to.equal(record.name); + + const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); + const recordId: string = await cloudflareClient.getRecordIdByName(record.name); + + await cloudflareClient.removeRecord(zoneId, recordId); + }); + + + it('should sync with ip via parameter', async() => { + const record: IRecord = authConfig.records[0]; + const randomIp: string = getRandomIp(); + + const recordData: Record = await cloudflareClient.syncRecord(record, randomIp); + + expect(recordData.name).to.equal(record.name); + expect(recordData.content).to.equal(randomIp); + + const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); + const recordId: string = await cloudflareClient.getRecordIdByName(record.name); + + await cloudflareClient.removeRecord(zoneId, recordId); + }); + + it('should sync with ip via record.content', async() => { + const record: IRecord = authConfig.records[0]; + const randomIp: string = getRandomIp(); + record.content = randomIp; + + const recordData: Record = await cloudflareClient.syncRecord(record); + + expect(recordData.name).to.equal(record.name); + expect(recordData.content).to.equal(randomIp); + + const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); + const recordId: string = await cloudflareClient.getRecordIdByName(record.name); + + await cloudflareClient.removeRecord(zoneId, recordId); + }); + + + it('should sync with external ip', async() => { + const record: IRecord = authConfig.records[0]; + record.content = undefined; + const currentIp: string = await IPUtils.getIp(); + + const recordData: Record = await cloudflareClient.syncRecord(record); + + expect(recordData.name).to.equal(record.name); + expect(recordData.content).to.equal(currentIp); + + const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); + const recordId: string = await cloudflareClient.getRecordIdByName(record.name); + + await cloudflareClient.removeRecord(zoneId, recordId); + }); +}); + +function getRandomIp(): string { + return `${getRandomNumber()}.${getRandomNumber()}.${getRandomNumber()}.${getRandomNumber()}`; +} + +function getRandomNumber(): number { + return Math.floor(Math.random() * 9) + 1; +} \ No newline at end of file From d1bf4cd7eff5c83a2616239ea607cf72f12ce916 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 00:57:40 +0200 Subject: [PATCH 22/70] :arrow_up: Update 'typescript' --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d4eeeda..cecdf9e 100755 --- a/package.json +++ b/package.json @@ -36,6 +36,6 @@ "chai": "4.2.0", "mocha": "6.2.0", "tslint": "5.20.0", - "typescript": "3.6.2" + "typescript": "3.6.3" } } From 2d9679694218e38a9044b8337bde7b9d4f79ef2e Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 00:58:09 +0200 Subject: [PATCH 23/70] :rotating_light: Fix Lint Errors --- src/contracts/IRecord.ts | 10 ++-- src/contracts/cloudflare/Record.ts | 4 +- src/contracts/cloudflare/Zone.ts | 8 +-- src/index.ts | 15 +++-- src/lib/cloudflare-client.ts | 89 ++++++++++++++--------------- src/lib/cron.ts | 4 +- src/lib/ip-utils.ts | 16 +++--- src/tests/cloudflare-client-test.ts | 14 ++--- src/tests/cron-tests.ts | 1 + src/tests/ip-utils-test.ts | 3 +- tslint.json | 2 +- 11 files changed, 82 insertions(+), 84 deletions(-) diff --git a/src/contracts/IRecord.ts b/src/contracts/IRecord.ts index 8d03420..b2e7659 100644 --- a/src/contracts/IRecord.ts +++ b/src/contracts/IRecord.ts @@ -1,8 +1,8 @@ export interface IRecord { - name: string, - type?: string, - proxied?: boolean, - ttl?: number, - priority?: number, + name: string; + type?: string; + proxied?: boolean; + ttl?: number; + priority?: number; content?: string; } diff --git a/src/contracts/cloudflare/Record.ts b/src/contracts/cloudflare/Record.ts index bbf3f6c..e64772f 100644 --- a/src/contracts/cloudflare/Record.ts +++ b/src/contracts/cloudflare/Record.ts @@ -12,10 +12,10 @@ export type Record = { modified_on: string; created_on: string; meta: RecordMetaData; -} +}; export type RecordMetaData = { auto_added: boolean; managed_by_apps: boolean; managed_by_argo_tunnel: boolean; -} +}; diff --git a/src/contracts/cloudflare/Zone.ts b/src/contracts/cloudflare/Zone.ts index 0eaa48d..5723057 100644 --- a/src/contracts/cloudflare/Zone.ts +++ b/src/contracts/cloudflare/Zone.ts @@ -16,7 +16,7 @@ export type Zone = { custom_certificate_quota: number, page_rule_quota: number, phishing_detected: boolean, - multiple_railguns_allowed: boolean + multiple_railguns_allowed: boolean, }; owner: { id: string, @@ -25,7 +25,7 @@ export type Zone = { }; account: { id: string, - name: string + name: string, }; permissions: Array; plan: { @@ -38,6 +38,6 @@ export type Zone = { can_subscribe: boolean, legacy_id: string, legacy_discount: boolean, - externally_managed: boolean + externally_managed: boolean, } -} +}; diff --git a/src/index.ts b/src/index.ts index e9e4f36..fdcfb89 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,9 +6,9 @@ import ipUtils from './lib/ip-utils'; import { IRecord, - Record, MultiSyncCallback, MultiSyncResult, + Record, SingleSyncResult, } from './contracts/index'; @@ -35,7 +35,6 @@ export default class CloudflareDDNSSync { const zoneId: string = await this.cloudflareClient.getZoneIdByRecordName(recordName); const recordId: string = await this.cloudflareClient.getRecordIdByName(recordName); - this.cloudflareClient.removeRecord(zoneId, recordId); } @@ -56,17 +55,17 @@ export default class CloudflareDDNSSync { public async syncOnIpChange(records: Array, callback: MultiSyncCallback): Promise { const changeListenerId: string = await ipUtils.addIpChangeListener(async(ip: string) => { - const result = await this.syncRecords(records, ip); - + const result: Array = await this.syncRecords(records, ip); + callback(result); }); const currentIp: string = await ipUtils.getIp(); - this.syncRecords(records, currentIp).then((records: Array) => { - callback(records); + this.syncRecords(records, currentIp).then((syncedRecords: Array) => { + callback(syncedRecords); }); - return changeListenerId + return changeListenerId; } public stopSyncOnIpChange(changeListenerId: string): void { @@ -78,7 +77,7 @@ export default class CloudflareDDNSSync { const result: Array = await this.syncRecords(records, ip); callback(result); - }) + }); } } diff --git a/src/lib/cloudflare-client.ts b/src/lib/cloudflare-client.ts index daa146b..9a20340 100644 --- a/src/lib/cloudflare-client.ts +++ b/src/lib/cloudflare-client.ts @@ -1,10 +1,10 @@ import Cloudflare from 'cloudflare'; import parseDomain from 'parse-domain'; -import {IRecord, Record, Zone, ZoneMap} from "../contracts"; +import {IRecord, Record, Zone, ZoneMap} from '../contracts'; export default class CloudflareClient { - private cloudflare; + private cloudflare: Cloudflare; private zoneMap: ZoneMap = new Map(); @@ -24,12 +24,12 @@ export default class CloudflareClient { const recordId: string = recordIds.get(record.name); const recordExists: boolean = recordId !== undefined; - if(recordExists) { - const result = await this.updateRecord(zoneId, recordId, record, ip); + if (recordExists) { + const result: Record = await this.updateRecord(zoneId, recordId, record, ip); return result; } else { - const result = await this.createRecord(zoneId, record, ip); + const result: Record = await this.createRecord(zoneId, record, ip); return result; } @@ -38,18 +38,18 @@ export default class CloudflareClient { public async syncRecords(records: Array, ip?: string): Promise> { const recordIds: Map = await this.getRecordIdsForRecords(records); - const resultPromises: Array> = records.map(async (record: IRecord) => { + const resultPromises: Array> = records.map(async(record: IRecord) => { const zoneId: string = await this.getZoneIdByRecordName(record.name); const recordId: string = recordIds.get(record.name); const recordExists: boolean = recordId !== undefined; - if(recordExists) { - const currentResult = await this.updateRecord(zoneId, recordId, record, ip); - + if (recordExists) { + const currentResult: Record = await this.updateRecord(zoneId, recordId, record, ip); + return currentResult; } else { - const currentResult = await this.createRecord(zoneId, record, ip); - + const currentResult: Record = await this.createRecord(zoneId, record, ip); + return currentResult; } @@ -63,7 +63,7 @@ export default class CloudflareClient { public async removeRecord(zoneId: string, recordId: string): Promise { try { await this.cloudflare.dnsRecords.del(zoneId, recordId); - } catch(error) { + } catch (error) { throw error; } } @@ -85,14 +85,14 @@ export default class CloudflareClient { console.time('getRecordDataForRecord'); const domain: string = this.getDomainByRecordName(record.name); - const recordDataForDomain: Array = await this.getRecordsByDomain(domain); + const recordDataForDomain: Array = await this.getRecordsByDomain(domain); - const recordData: Record = recordDataForDomain.find((singleRecordData: Record) => { + const recordData: Record = recordDataForDomain.find((singleRecordData: Record) => { return record.name === singleRecordData.name; }); - console.timeEnd('getRecordDataForRecord'); - return recordData; + console.timeEnd('getRecordDataForRecord'); + return recordData; } // TODO: Performance? @@ -100,16 +100,16 @@ export default class CloudflareClient { console.time('getRecordDataForRecords'); const domains: Array = this.getDomainsFromRecords(records); - const recordDataPromises: Array>> = domains.map(async (domain: string) => { + const recordDataPromises: Array>> = domains.map(async(domain: string) => { const recordDataForDomain: Array = await this.getRecordsByDomain(domain); - const recordData: Array = recordDataForDomain.filter((singleRecordData: Record) => { + const recordDataForDomainFilteredByRecords: Array = recordDataForDomain.filter((singleRecordData: Record) => { return records.some((record: IRecord) => { return record.name === singleRecordData.name; }); }); - return recordData; + return recordDataForDomainFilteredByRecords; }); const recordDataForDomains: Array> = await Promise.all(recordDataPromises); @@ -124,15 +124,15 @@ export default class CloudflareClient { copyOfRecord.content = copyOfRecord.content ? copyOfRecord.content : ip; copyOfRecord.type = copyOfRecord.type ? copyOfRecord.type : 'A'; - if(!copyOfRecord.content) { - throw Error(`Could not create Record "${record.name}": Ip is missing!`) + if (!copyOfRecord.content) { + throw Error(`Could not create Record "${record.name}": Ip is missing!`); } try { - const response = await this.cloudflare.dnsRecords.add(zoneId, copyOfRecord); + const response: {result: Record} = await this.cloudflare.dnsRecords.add(zoneId, copyOfRecord); return response.result; - } catch(error) { + } catch (error) { throw error; } } @@ -142,25 +142,25 @@ export default class CloudflareClient { copyOfRecord.content = copyOfRecord.content ? copyOfRecord.content : ip; copyOfRecord.type = copyOfRecord.type ? copyOfRecord.type : 'A'; - if(!copyOfRecord.content) { - throw Error(`Could not update Record "${record.name}": Ip is missing!`) + if (!copyOfRecord.content) { + throw Error(`Could not update Record "${record.name}": Ip is missing!`); } try { - const response = await this.cloudflare.dnsRecords.edit(zoneId, recordId, copyOfRecord); + const response: {result: Record} = await this.cloudflare.dnsRecords.edit(zoneId, recordId, copyOfRecord); return response.result; - } catch(error) { + } catch (error) { throw error; } } private async updateZoneMap(): Promise { - const response = await this.cloudflare.zones.browse(); + const response: {result: Array} = await this.cloudflare.zones.browse(); const zones: Array = response.result; this.zoneMap = new Map(); - for(const zone of zones) { + for (const zone of zones) { this.zoneMap.set(zone.name, zone.id); } } @@ -170,12 +170,12 @@ export default class CloudflareClient { const records: Array = await this.getRecordsByDomain(domain); - const record: Record = records.find((record: Record) => { - return record.name === recordName; + const record: Record = records.find((currentRecord: Record) => { + return currentRecord.name === recordName; }); const recordNotFound: boolean = record === undefined; - if(recordNotFound) { + if (recordNotFound) { throw new Error(`Record '${recordName}' not found.`); } @@ -190,7 +190,7 @@ export default class CloudflareClient { const recordData: Array = await this.getRecordDataForRecords(records); - for(const record of recordData) { + for (const record of recordData) { recordIdMap.set(record.name, record.id); } @@ -202,39 +202,38 @@ export default class CloudflareClient { private async getRecordsByDomain(domain: string): Promise> { const zoneId: string = await this.getZoneIdByDomain(domain); - const response = await this.cloudflare.dnsRecords.browse(zoneId); + const response: {result: Array} = await this.cloudflare.dnsRecords.browse(zoneId); const records: Array = response.result; return records; } private async getZoneIdByDomain(domain: string): Promise { - if(this.zoneMap.has(domain)) { + if (this.zoneMap.has(domain)) { const zoneId: string = this.zoneMap.get(domain); return zoneId; - } - - - await this.updateZoneMap(); - const zoneId: string = this.zoneMap.get(domain); + } else { + await this.updateZoneMap(); + const zoneId: string = this.zoneMap.get(domain); - return zoneId; + return zoneId; + } } private getDomainsFromRecords(records: Array): Array { const domains: Array = records.map((record: IRecord) => { return this.getDomainByRecordName(record.name); - }).filter((domain: string, index: number, domains: Array) => { - return domains.indexOf(domain) == index; + }).filter((domain: string, index: number, domainList: Array) => { + return domainList.indexOf(domain) === index; }); return domains; } private getDomainByRecordName(recordName: string): string { - const parsedDomain = parseDomain(recordName); + const parsedDomain: parseDomain.ParsedDomain = parseDomain(recordName); return `${parsedDomain.domain}.${parsedDomain.tld}`; } -} \ No newline at end of file +} diff --git a/src/lib/cron.ts b/src/lib/cron.ts index 0037534..7d8c1eb 100644 --- a/src/lib/cron.ts +++ b/src/lib/cron.ts @@ -3,8 +3,8 @@ import cron, {ScheduledTask} from 'node-cron'; export default class Cron { public static createCronJob(cronExpression: string, callback: Function): ScheduledTask { const cronExpressionIsInvalid: boolean = !this.isValid(cronExpression); - if(cronExpressionIsInvalid) { - throw new Error(`'${cronExpression}' is not a valid cron expression.\nHere you can see how cron expressions work: https://cds.knaup.pw/cron-expression`) + if (cronExpressionIsInvalid) { + throw new Error(`'${cronExpression}' is not a valid cron expression.\nHere you can see how cron expressions work: https://cds.knaup.pw/cron-expression`); } return cron.schedule(cronExpression, () => { diff --git a/src/lib/ip-utils.ts b/src/lib/ip-utils.ts index b781969..356b83b 100644 --- a/src/lib/ip-utils.ts +++ b/src/lib/ip-utils.ts @@ -1,5 +1,5 @@ -import wimIp from 'what-is-my-ip-address'; import publicIp from 'public-ip'; +import wimIp from 'what-is-my-ip-address'; export default class IPUtils { private static readonly ipPollingDelay: number = 10 * 1000; @@ -8,20 +8,20 @@ export default class IPUtils { public static async getIp(): Promise { try { - return await publicIp.v4() + return await publicIp.v4(); } catch (error) { return wimIp.v4(); } } - + public static async addIpChangeListener(callback: Function): Promise { const eventListenerId: string = this.getRandomId(); let previousIp: string = await this.getIp(); - const intervalId: NodeJS.Timeout = setInterval(async () => { + const intervalId: NodeJS.Timeout = setInterval(async() => { const currentIp: string = await this.getIp(); - + const ipMustBeUpdated: boolean = currentIp !== previousIp; if (ipMustBeUpdated) { previousIp = currentIp; @@ -34,10 +34,10 @@ export default class IPUtils { return eventListenerId; } - + public static removeIpChangeListener(eventListenerId: string): void { const eventListenerIntervalId: NodeJS.Timeout = this.ipChangeEventListeners.get(eventListenerId); - + clearInterval(eventListenerIntervalId); IPUtils.ipChangeEventListeners.delete(eventListenerId); @@ -47,7 +47,7 @@ export default class IPUtils { const beginningRandomString: string = Math.random().toString(36).substr(2); const currentDateAsString: string = new Date().valueOf().toString(36); const endingRandomString: string = Math.random().toString(36).substr(2); - + const randomString: string = beginningRandomString + currentDateAsString + endingRandomString; return randomString; diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts index c27a7ff..5d556ce 100644 --- a/src/tests/cloudflare-client-test.ts +++ b/src/tests/cloudflare-client-test.ts @@ -1,14 +1,15 @@ +// tslint:disable:no-unused-expression import chai from 'chai'; import CloudflareClient from '../lib/cloudflare-client'; -import * as authConfig from './auth.json'; import {IRecord, Record} from '../contracts'; import IPUtils from '../lib/ip-utils'; +import * as authConfig from './auth.json'; -const expect = chai.expect; +const expect: Chai.ExpectStatic = chai.expect; -const cloudflareClient: CloudflareClient = new CloudflareClient(authConfig.auth.email, authConfig.auth.key) +const cloudflareClient: CloudflareClient = new CloudflareClient(authConfig.auth.email, authConfig.auth.key); describe('Cloudflare Client', () => { it('should get the zone id of a domain', async() => { @@ -40,7 +41,6 @@ describe('Cloudflare Client', () => { const record: IRecord = authConfig.records[0]; const recordId: string = await cloudflareClient.getRecordIdByName(record.name); - expect(recordId).to.be.string; expect(recordId.length).to.be.greaterThan(0); }); @@ -66,7 +66,7 @@ describe('Cloudflare Client', () => { const recordDataNames: Array = recordData.map((recordDataEntry: Record) => { return recordDataEntry.name; - }) + }); expect(recordData.length).to.equal(records.length); expect(recordDataNames).to.contain(records[0].name); @@ -92,7 +92,6 @@ describe('Cloudflare Client', () => { await cloudflareClient.removeRecord(zoneId, recordId); }); - it('should sync with ip via parameter', async() => { const record: IRecord = authConfig.records[0]; const randomIp: string = getRandomIp(); @@ -124,7 +123,6 @@ describe('Cloudflare Client', () => { await cloudflareClient.removeRecord(zoneId, recordId); }); - it('should sync with external ip', async() => { const record: IRecord = authConfig.records[0]; record.content = undefined; @@ -148,4 +146,4 @@ function getRandomIp(): string { function getRandomNumber(): number { return Math.floor(Math.random() * 9) + 1; -} \ No newline at end of file +} diff --git a/src/tests/cron-tests.ts b/src/tests/cron-tests.ts index fa65c84..5c7656b 100644 --- a/src/tests/cron-tests.ts +++ b/src/tests/cron-tests.ts @@ -1,3 +1,4 @@ +// tslint:disable:no-unused-expression import chai from 'chai'; import {ScheduledTask} from 'node-cron'; diff --git a/src/tests/ip-utils-test.ts b/src/tests/ip-utils-test.ts index a845416..73deb77 100644 --- a/src/tests/ip-utils-test.ts +++ b/src/tests/ip-utils-test.ts @@ -1,8 +1,9 @@ +// tslint:disable:no-unused-expression import chai from 'chai'; import IpUtils from '../lib/ip-utils'; -const expect = chai.expect; +const expect: Chai.ExpectStatic = chai.expect; const ipRegex: RegExp = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/; describe('IPUtils', () => { diff --git a/tslint.json b/tslint.json index b857b3d..ed40555 100644 --- a/tslint.json +++ b/tslint.json @@ -93,7 +93,7 @@ "max-classes-per-file": [true, 1], "max-file-line-count": [true, 3000], "max-line-length": [true, 150], - "no-default-export": true, + "no-default-export": false, "no-mergeable-namespace": true, "no-require-imports": true, "no-trailing-whitespace": true, From 14f094c9d20d66ae4b9c57d938c4140e12c1f925 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 18:53:53 +0200 Subject: [PATCH 24/70] :bug: Fix Getting External IP in Cloudflare Client --- src/lib/cloudflare-client.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib/cloudflare-client.ts b/src/lib/cloudflare-client.ts index 9a20340..8237b25 100644 --- a/src/lib/cloudflare-client.ts +++ b/src/lib/cloudflare-client.ts @@ -2,6 +2,7 @@ import Cloudflare from 'cloudflare'; import parseDomain from 'parse-domain'; import {IRecord, Record, Zone, ZoneMap} from '../contracts'; +import IPUtils from './ip-utils'; export default class CloudflareClient { private cloudflare: Cloudflare; @@ -19,17 +20,18 @@ export default class CloudflareClient { public async syncRecord(record: IRecord, ip?: string): Promise { const recordIds: Map = await this.getRecordIdsForRecords([record]); + const ipToUse: string = ip ? ip : await IPUtils.getIp(); const zoneId: string = await this.getZoneIdByRecordName(record.name); const recordId: string = recordIds.get(record.name); const recordExists: boolean = recordId !== undefined; if (recordExists) { - const result: Record = await this.updateRecord(zoneId, recordId, record, ip); + const result: Record = await this.updateRecord(zoneId, recordId, record, ipToUse); return result; } else { - const result: Record = await this.createRecord(zoneId, record, ip); + const result: Record = await this.createRecord(zoneId, record, ipToUse); return result; } @@ -37,6 +39,7 @@ export default class CloudflareClient { public async syncRecords(records: Array, ip?: string): Promise> { const recordIds: Map = await this.getRecordIdsForRecords(records); + const ipToUse: string = ip ? ip : await IPUtils.getIp(); const resultPromises: Array> = records.map(async(record: IRecord) => { const zoneId: string = await this.getZoneIdByRecordName(record.name); @@ -44,11 +47,11 @@ export default class CloudflareClient { const recordExists: boolean = recordId !== undefined; if (recordExists) { - const currentResult: Record = await this.updateRecord(zoneId, recordId, record, ip); + const currentResult: Record = await this.updateRecord(zoneId, recordId, record, ipToUse); return currentResult; } else { - const currentResult: Record = await this.createRecord(zoneId, record, ip); + const currentResult: Record = await this.createRecord(zoneId, record, ipToUse); return currentResult; } From 36559b8ee2610ceb28c4c8ce92d3d1f0009c10dd Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 18:54:18 +0200 Subject: [PATCH 25/70] :bug: Fix Using 'what-is-my-ip-address' --- src/lib/ip-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/ip-utils.ts b/src/lib/ip-utils.ts index 356b83b..dbefb7d 100644 --- a/src/lib/ip-utils.ts +++ b/src/lib/ip-utils.ts @@ -1,5 +1,5 @@ import publicIp from 'public-ip'; -import wimIp from 'what-is-my-ip-address'; +import {v4 as wimIp} from 'what-is-my-ip-address'; export default class IPUtils { private static readonly ipPollingDelay: number = 10 * 1000; @@ -10,7 +10,7 @@ export default class IPUtils { try { return await publicIp.v4(); } catch (error) { - return wimIp.v4(); + return wimIp(); } } From ac8e3d825c8473036f6c631ddfc5b58c71f09f8a Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 18:57:13 +0200 Subject: [PATCH 26/70] :recycle: Refactor Usage of Cloudflare Auth --- package.json | 2 ++ src/tests/auth/auth-service.ts | 38 +++++++++++++++++++++++++++++ src/tests/{ => auth}/auth.json | 0 src/tests/cloudflare-client-test.ts | 25 ++++++++++--------- 4 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 src/tests/auth/auth-service.ts rename src/tests/{ => auth}/auth.json (100%) diff --git a/package.json b/package.json index cecdf9e..fbe7781 100755 --- a/package.json +++ b/package.json @@ -30,10 +30,12 @@ }, "devDependencies": { "@types/chai": "4.2.2", + "@types/minimist": "1.2.0", "@types/mocha": "5.2.7", "@types/node": "10.14.17", "@types/node-cron": "2.0.2", "chai": "4.2.0", + "minimist": "1.2.0", "mocha": "6.2.0", "tslint": "5.20.0", "typescript": "3.6.3" diff --git a/src/tests/auth/auth-service.ts b/src/tests/auth/auth-service.ts new file mode 100644 index 0000000..8ea5571 --- /dev/null +++ b/src/tests/auth/auth-service.ts @@ -0,0 +1,38 @@ +import minimist, {ParsedArgs} from 'minimist'; + +import authConfig from './auth.json'; + +import {IRecord} from '../../contracts/index.js'; + +export default class AuthService { + public static getAuthData(): AuthData { + const args: ParsedArgs = minimist(process.argv.slice(2)); + + const authData: AuthData = Object.assign({}, authConfig); + + const email: string | undefined = args.email; + const key: string | undefined = args.key; + const recordsAsString: string | undefined = args.records; + + authData.auth.email = email ? email : authData.auth.email; + authData.auth.key = key ? key : authData.auth.key; + + try { + const records: Array = JSON.parse(recordsAsString).records; + + authData.records = records; + } catch { + // Do nothing + } + + return authData; + } +} + +export type AuthData = { + auth: { + email: string, + key: string, + }, + records: Array, +}; diff --git a/src/tests/auth.json b/src/tests/auth/auth.json similarity index 100% rename from src/tests/auth.json rename to src/tests/auth/auth.json diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts index 5d556ce..2841b17 100644 --- a/src/tests/cloudflare-client-test.ts +++ b/src/tests/cloudflare-client-test.ts @@ -2,18 +2,19 @@ import chai from 'chai'; import CloudflareClient from '../lib/cloudflare-client'; +import IPUtils from '../lib/ip-utils'; +import Auth, {AuthData} from './auth/auth-service'; import {IRecord, Record} from '../contracts'; -import IPUtils from '../lib/ip-utils'; -import * as authConfig from './auth.json'; const expect: Chai.ExpectStatic = chai.expect; -const cloudflareClient: CloudflareClient = new CloudflareClient(authConfig.auth.email, authConfig.auth.key); +const authData: AuthData = Auth.getAuthData(); +const cloudflareClient: CloudflareClient = new CloudflareClient(authData.auth.email, authData.auth.key); describe('Cloudflare Client', () => { it('should get the zone id of a domain', async() => { - const record: IRecord = authConfig.records[0]; + const record: IRecord = authData.records[0]; const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); expect(zoneId).to.be.string; @@ -22,7 +23,7 @@ describe('Cloudflare Client', () => { describe('Add, Get, Remove', () => { it('should be able to create a record', async() => { - const record: IRecord = authConfig.records[0]; + const record: IRecord = authData.records[0]; record.content = '1.2.3.4'; @@ -38,7 +39,7 @@ describe('Cloudflare Client', () => { }); it('should be able to get a record id', async() => { - const record: IRecord = authConfig.records[0]; + const record: IRecord = authData.records[0]; const recordId: string = await cloudflareClient.getRecordIdByName(record.name); expect(recordId).to.be.string; @@ -46,7 +47,7 @@ describe('Cloudflare Client', () => { }); it('should be able to remove a record', async() => { - const record: IRecord = authConfig.records[0]; + const record: IRecord = authData.records[0]; const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); const recordId: string = await cloudflareClient.getRecordIdByName(record.name); @@ -56,7 +57,7 @@ describe('Cloudflare Client', () => { }); it('should get record data for records', async() => { - const records: Array = authConfig.records; + const records: Array = authData.records; for (const record of records) { await cloudflareClient.syncRecord(record, '1.2.3.4'); @@ -79,7 +80,7 @@ describe('Cloudflare Client', () => { }); it('should sync existing record', async() => { - const record: IRecord = authConfig.records[0]; + const record: IRecord = authData.records[0]; await cloudflareClient.syncRecord(record); const recordData: Record = await cloudflareClient.syncRecord(record); @@ -93,7 +94,7 @@ describe('Cloudflare Client', () => { }); it('should sync with ip via parameter', async() => { - const record: IRecord = authConfig.records[0]; + const record: IRecord = authData.records[0]; const randomIp: string = getRandomIp(); const recordData: Record = await cloudflareClient.syncRecord(record, randomIp); @@ -108,7 +109,7 @@ describe('Cloudflare Client', () => { }); it('should sync with ip via record.content', async() => { - const record: IRecord = authConfig.records[0]; + const record: IRecord = authData.records[0]; const randomIp: string = getRandomIp(); record.content = randomIp; @@ -124,7 +125,7 @@ describe('Cloudflare Client', () => { }); it('should sync with external ip', async() => { - const record: IRecord = authConfig.records[0]; + const record: IRecord = authData.records[0]; record.content = undefined; const currentIp: string = await IPUtils.getIp(); From 3cda7f683b55deb59f0669256748748927e68418 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 18:58:14 +0200 Subject: [PATCH 27/70] :mute: Stop Logging Time --- src/lib/cloudflare-client.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/cloudflare-client.ts b/src/lib/cloudflare-client.ts index 8237b25..1f7fefd 100644 --- a/src/lib/cloudflare-client.ts +++ b/src/lib/cloudflare-client.ts @@ -85,7 +85,7 @@ export default class CloudflareClient { // TODO: Performance? public async getRecordDataForRecord(record: IRecord): Promise { - console.time('getRecordDataForRecord'); + // console.time('getRecordDataForRecord'); const domain: string = this.getDomainByRecordName(record.name); const recordDataForDomain: Array = await this.getRecordsByDomain(domain); @@ -94,13 +94,13 @@ export default class CloudflareClient { return record.name === singleRecordData.name; }); - console.timeEnd('getRecordDataForRecord'); + // console.timeEnd('getRecordDataForRecord'); return recordData; } // TODO: Performance? public async getRecordDataForRecords(records: Array): Promise> { - console.time('getRecordDataForRecords'); + // console.time('getRecordDataForRecords'); const domains: Array = this.getDomainsFromRecords(records); const recordDataPromises: Array>> = domains.map(async(domain: string) => { @@ -118,7 +118,7 @@ export default class CloudflareClient { const recordDataForDomains: Array> = await Promise.all(recordDataPromises); const recordData: Array = [].concat(...recordDataForDomains); - console.timeEnd('getRecordDataForRecords'); + // console.timeEnd('getRecordDataForRecords'); return recordData; } @@ -187,7 +187,7 @@ export default class CloudflareClient { // TODO: Performance? private async getRecordIdsForRecords(records: Array): Promise> { - console.time('getRecordIdsToUpdate'); + // console.time('getRecordIdsToUpdate'); const recordIdMap: Map = new Map(); @@ -197,7 +197,7 @@ export default class CloudflareClient { recordIdMap.set(record.name, record.id); } - console.timeEnd('getRecordIdsToUpdate'); + // console.timeEnd('getRecordIdsToUpdate'); return recordIdMap; } From 61f85d612609c7c65535d6e3d4c37281e44648d9 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 23:08:59 +0200 Subject: [PATCH 28/70] :memo: Fix Checkboxes in Pull Request Template --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7ffcdf2..8e2f1d7 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -12,5 +12,5 @@ PR: #NumberOfThisPR ## Checks -[ ] The code is covered by tests. -[ ] The code is ready to get merged. +- [ ] The code is covered by tests. +- [ ] The code is ready to get merged. From d2963314568a22de108f07d53d4f612ada4dd90c Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 23:10:58 +0200 Subject: [PATCH 29/70] :construction_worker: Add Jenkinsfile --- Jenkinsfile | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 +- 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..73c2613 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,133 @@ +#!/usr/bin/env groovy + +def cleanup_workspace() { + cleanWs() + dir("${env.WORKSPACE}@tmp") { + deleteDir() + } + dir("${env.WORKSPACE}@script") { + deleteDir() + } + dir("${env.WORKSPACE}@script@tmp") { + deleteDir() + } +} + +def setBuildStatus(String message, String state) { + step([ + $class: "GitHubCommitStatusSetter", + reposSource: [$class: "ManuallyEnteredRepositorySource", url: "/~https://github.com/SteffenKn/Cloudflare-DDNS-Sync"], + contextSource: [$class: "ManuallyEnteredCommitContextSource", context: "ci/jenkins/build-status"], + errorHandlers: [[$class: "ChangingBuildStatusErrorHandler", result: "UNSTABLE"]], + statusResultSource: [ $class: "ConditionalStatusResultSource", results: [[$class: "AnyBuildResult", message: message, state: state]] ] + ]); +} + +pipeline { + agent any + tools { + nodejs "nodejs-lts" + } + environment { + NODE_JS_VERSION = 'nodejs-lts' + } + + stages { + stage('Prepare') { + steps { + setBuildStatus('Building...', 'PENDING') + + script { + raw_package_version = sh(script: 'node --print --eval "require(\'./package.json\').version"', returnStdout: true) + package_version = raw_package_version.trim() + branch = env.BRANCH_NAME; + branch_is_master = branch == 'master'; + + echo("Package version is '${package_version}'") + echo("Branch is '${branch}'") + } + + nodejs(configId: env.NPM_RC_FILE, nodeJSInstallationName: env.NODE_JS_VERSION) { + sh('node --version') + } + } + } + + stage('Install') { + steps { + sh('node --version') + + sh('npm install') + } + } + + stage('Lint') { + steps { + sh('node --version') + + sh('npm run lint') + } + } + + stage('Build') { + steps { + sh('node --version') + + sh('npm run build') + } + } + + stage('Test') { + steps { + sh('node --version') + + withCredentials([string(credentialsId: 'CLOUDFLARE_EMAIL', variable: 'CLOUDFLARE_EMAIL'), string(credentialsId: 'CLOUDFLARE_KEY', variable: 'CLOUDFLARE_KEY'), string(credentialsId: 'CLOUDFLARE_RECORDS', variable: 'CLOUDFLARE_RECORDS')]) { + sh('npm run test-jenkins -- --email="'+CLOUDFLARE_EMAIL+'" --key="'+CLOUDFLARE_KEY+'" --records="'+CLOUDFLARE_RECORDS+'"') + + junit 'report.xml' + } + } + } + + stage('Publish') { + when { + expression { + branch_is_master + } + } + steps { + sh('node --version') + + withNPM(npmrcConfig: 'Jenkins-Npmrc') { + sh('npm publish') + } + } + } + + stage('Cleanup') { + steps { + script { + // this stage just exists, so the cleanup-work that happens in the post-script + // will show up in its own stage in Blue Ocean + sh(script: ':', returnStdout: true); + } + } + } + } + + post { + always { + script { + cleanup_workspace(); + } + } + + success { + setBuildStatus('Build succeeded.', 'SUCCESS'); + } + + failure { + setBuildStatus('Build failed!', 'FAILURE'); + } + } +} diff --git a/package.json b/package.json index fbe7781..b7feee9 100755 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "scripts": { "build": "tsc", "lint": "tslint --project .", - "test": "mocha dist/tests/*.js --timeout 15000 --exit" + "test": "mocha dist/tests/*.js --timeout 15000 --exit", + "test-jenkins": "JUNIT_REPORT_PATH=report.xml npm test -- --reporter mocha-jenkins-reporter" }, "dependencies": { "cloudflare": "2.6.0", @@ -37,6 +38,7 @@ "chai": "4.2.0", "minimist": "1.2.0", "mocha": "6.2.0", + "mocha-jenkins-reporter": "0.4.2", "tslint": "5.20.0", "typescript": "3.6.3" } From 70af6ebec037711f24afe95b01c9fcd61828a3bf Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 23:19:27 +0200 Subject: [PATCH 30/70] :memo: Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 8ac696d..bb425a4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018, Steffen Knaup +Copyright (c) 2019, Steffen Knaup Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. From 6b15f8bd05d46809aab7c32a1c6a138412450843 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 20:21:34 +0200 Subject: [PATCH 31/70] :sparkles: Add Github Actions Workflow for Push --- .github/workflows/push.yml | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/push.yml diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 0000000..f74e25e --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,39 @@ +name: Test-Workflow + +on: + push: + branches: + - develop + - feature/* + - release/* + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: npm install + run: npm install + + - name: npm lint + run: npm run lint + + - name: npm build + run: npm run build + + - name: npm test + env: + CLOUDFLARE_EMAIL: ${{ secrets.TESTS__CLOUDFLARE_EMAIL }} + CLOUDFLARE_KEY: ${{ secrets.TESTS__CLOUDFLARE_KEY }} + CLOUDFLARE_RECORDS: ${{ secrets.TESTS__CLOUDFLARE_RECORDS }} + run: npm test -- --email=$CLOUDFLARE_EMAIL --key=$CLOUDFLARE_KEY --records=$CLOUDFLARE_RECORDS From e73c80c9d5cc56111d0f2f49b60d412825fb2c8c Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Sep 2019 20:27:32 +0200 Subject: [PATCH 32/70] :sparkles: Add Github Workflow Badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ef73f40..fc92d71 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Cloudflare-DDNS-Sync +![](/~https://github.com/SteffenKn/Cloudflare-ddns-sync/workflows/Test-Workflow/badge.svg) + [![NPM](https://nodei.co/npm/cloudflare-ddns-sync.png)](https://www.npmjs.com/package/cloudflare-ddns-sync) [Documentation](https://cds.knaup.pw/) From 3a5ff90f9272bdef1231f9baca3e8cca95f926f3 Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 13 Sep 2019 17:29:30 +0200 Subject: [PATCH 33/70] :lipstick: Improve Jenkins Commit Status Name --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 73c2613..f0c366b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -17,7 +17,7 @@ def setBuildStatus(String message, String state) { step([ $class: "GitHubCommitStatusSetter", reposSource: [$class: "ManuallyEnteredRepositorySource", url: "/~https://github.com/SteffenKn/Cloudflare-DDNS-Sync"], - contextSource: [$class: "ManuallyEnteredCommitContextSource", context: "ci/jenkins/build-status"], + contextSource: [$class: "ManuallyEnteredCommitContextSource", context: "👷‍♂️ Jenkins"], errorHandlers: [[$class: "ChangingBuildStatusErrorHandler", result: "UNSTABLE"]], statusResultSource: [ $class: "ConditionalStatusResultSource", results: [[$class: "AnyBuildResult", message: message, state: state]] ] ]); From 2ce92bc6841c0ab0cf9fe9fe38f467ec11131782 Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 13 Sep 2019 17:34:24 +0200 Subject: [PATCH 34/70] :construction_worker: Only Build Once --- .github/workflows/push.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index f74e25e..e96ec87 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -12,16 +12,13 @@ jobs: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [10.x, 12.x] - steps: - uses: actions/checkout@v1 - - name: Use Node.js ${{ matrix.node-version }} + - name: Use Node.js 10.x uses: actions/setup-node@v1 with: - node-version: ${{ matrix.node-version }} + node-version: 10.x + - name: npm install run: npm install From a745ca7438fe7ae750689777b7656d7c3a594b54 Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 13 Sep 2019 17:59:40 +0200 Subject: [PATCH 35/70] :construction_worker: Build Twice But Successively --- .github/workflows/push.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index e96ec87..d4d646e 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -12,13 +12,18 @@ jobs: runs-on: ubuntu-latest + strategy: + max-parallel: 1 + fail-fast: false + matrix: + node-version: [10.x, 12.x] + steps: - uses: actions/checkout@v1 - - name: Use Node.js 10.x + - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: - node-version: 10.x - + node-version: ${{ matrix.node-version }} - name: npm install run: npm install From 2cd4ea676ccd0c546de8d0d770e7499f70a24f81 Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 13 Sep 2019 17:18:58 +0200 Subject: [PATCH 36/70] :recycle: Rename Auth to TestService --- src/tests/cloudflare-client-test.ts | 24 +++++++++---------- .../auth.json => test-service/test-data.json} | 0 .../test-service.ts} | 18 +++++++------- 3 files changed, 21 insertions(+), 21 deletions(-) rename src/tests/{auth/auth.json => test-service/test-data.json} (100%) rename src/tests/{auth/auth-service.ts => test-service/test-service.ts} (58%) diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts index 2841b17..c9830ee 100644 --- a/src/tests/cloudflare-client-test.ts +++ b/src/tests/cloudflare-client-test.ts @@ -3,18 +3,18 @@ import chai from 'chai'; import CloudflareClient from '../lib/cloudflare-client'; import IPUtils from '../lib/ip-utils'; -import Auth, {AuthData} from './auth/auth-service'; +import TestService, {TestData} from './test-service/test-service'; import {IRecord, Record} from '../contracts'; const expect: Chai.ExpectStatic = chai.expect; -const authData: AuthData = Auth.getAuthData(); -const cloudflareClient: CloudflareClient = new CloudflareClient(authData.auth.email, authData.auth.key); +const testData: TestData = TestService.getTestData(); +const cloudflareClient: CloudflareClient = new CloudflareClient(testData.auth.email, testData.auth.key); describe('Cloudflare Client', () => { it('should get the zone id of a domain', async() => { - const record: IRecord = authData.records[0]; + const record: IRecord = testData.records[0]; const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); expect(zoneId).to.be.string; @@ -23,7 +23,7 @@ describe('Cloudflare Client', () => { describe('Add, Get, Remove', () => { it('should be able to create a record', async() => { - const record: IRecord = authData.records[0]; + const record: IRecord = testData.records[0]; record.content = '1.2.3.4'; @@ -39,7 +39,7 @@ describe('Cloudflare Client', () => { }); it('should be able to get a record id', async() => { - const record: IRecord = authData.records[0]; + const record: IRecord = testData.records[0]; const recordId: string = await cloudflareClient.getRecordIdByName(record.name); expect(recordId).to.be.string; @@ -47,7 +47,7 @@ describe('Cloudflare Client', () => { }); it('should be able to remove a record', async() => { - const record: IRecord = authData.records[0]; + const record: IRecord = testData.records[0]; const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); const recordId: string = await cloudflareClient.getRecordIdByName(record.name); @@ -57,7 +57,7 @@ describe('Cloudflare Client', () => { }); it('should get record data for records', async() => { - const records: Array = authData.records; + const records: Array = testData.records; for (const record of records) { await cloudflareClient.syncRecord(record, '1.2.3.4'); @@ -80,7 +80,7 @@ describe('Cloudflare Client', () => { }); it('should sync existing record', async() => { - const record: IRecord = authData.records[0]; + const record: IRecord = testData.records[0]; await cloudflareClient.syncRecord(record); const recordData: Record = await cloudflareClient.syncRecord(record); @@ -94,7 +94,7 @@ describe('Cloudflare Client', () => { }); it('should sync with ip via parameter', async() => { - const record: IRecord = authData.records[0]; + const record: IRecord = testData.records[0]; const randomIp: string = getRandomIp(); const recordData: Record = await cloudflareClient.syncRecord(record, randomIp); @@ -109,7 +109,7 @@ describe('Cloudflare Client', () => { }); it('should sync with ip via record.content', async() => { - const record: IRecord = authData.records[0]; + const record: IRecord = testData.records[0]; const randomIp: string = getRandomIp(); record.content = randomIp; @@ -125,7 +125,7 @@ describe('Cloudflare Client', () => { }); it('should sync with external ip', async() => { - const record: IRecord = authData.records[0]; + const record: IRecord = testData.records[0]; record.content = undefined; const currentIp: string = await IPUtils.getIp(); diff --git a/src/tests/auth/auth.json b/src/tests/test-service/test-data.json similarity index 100% rename from src/tests/auth/auth.json rename to src/tests/test-service/test-data.json diff --git a/src/tests/auth/auth-service.ts b/src/tests/test-service/test-service.ts similarity index 58% rename from src/tests/auth/auth-service.ts rename to src/tests/test-service/test-service.ts index 8ea5571..8389e41 100644 --- a/src/tests/auth/auth-service.ts +++ b/src/tests/test-service/test-service.ts @@ -1,35 +1,35 @@ import minimist, {ParsedArgs} from 'minimist'; -import authConfig from './auth.json'; +import testConfig from './test-data.json'; import {IRecord} from '../../contracts/index.js'; -export default class AuthService { - public static getAuthData(): AuthData { +export default class TestService { + public static getTestData(): TestData { const args: ParsedArgs = minimist(process.argv.slice(2)); - const authData: AuthData = Object.assign({}, authConfig); + const testData: TestData = Object.assign({}, testConfig); const email: string | undefined = args.email; const key: string | undefined = args.key; const recordsAsString: string | undefined = args.records; - authData.auth.email = email ? email : authData.auth.email; - authData.auth.key = key ? key : authData.auth.key; + testData.auth.email = email ? email : testData.auth.email; + testData.auth.key = key ? key : testData.auth.key; try { const records: Array = JSON.parse(recordsAsString).records; - authData.records = records; + testData.records = records; } catch { // Do nothing } - return authData; + return testData; } } -export type AuthData = { +export type TestData = { auth: { email: string, key: string, From b043a52e584f7fdf992d2c5cd4d1344b548f5bf6 Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 13 Sep 2019 17:20:50 +0200 Subject: [PATCH 37/70] :recycle: Refactor Test Cleanup --- src/tests/cloudflare-client-test.ts | 110 ++++++++++++---------------- 1 file changed, 47 insertions(+), 63 deletions(-) diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts index c9830ee..c9b2f56 100644 --- a/src/tests/cloudflare-client-test.ts +++ b/src/tests/cloudflare-client-test.ts @@ -3,62 +3,72 @@ import chai from 'chai'; import CloudflareClient from '../lib/cloudflare-client'; import IPUtils from '../lib/ip-utils'; -import TestService, {TestData} from './test-service/test-service'; +import TestService from './test-service/test-service'; import {IRecord, Record} from '../contracts'; const expect: Chai.ExpectStatic = chai.expect; -const testData: TestData = TestService.getTestData(); -const cloudflareClient: CloudflareClient = new CloudflareClient(testData.auth.email, testData.auth.key); +const cloudflareClient: CloudflareClient = new CloudflareClient(TestService.getTestData().auth.email, TestService.getTestData().auth.key); describe('Cloudflare Client', () => { + afterEach(async() => { + const records: Array = TestService.getTestData().records; + const recordData: Array = await cloudflareClient.getRecordDataForRecords(records); + + for (const record of recordData) { + const zoneId: string = record.zone_id; + + await cloudflareClient.removeRecord(zoneId, record.id); + } + }); + it('should get the zone id of a domain', async() => { - const record: IRecord = testData.records[0]; + const record: IRecord = TestService.getTestData().records[0]; const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); expect(zoneId).to.be.string; expect(zoneId.length).to.be.greaterThan(0); }); - describe('Add, Get, Remove', () => { - it('should be able to create a record', async() => { - const record: IRecord = testData.records[0]; - - record.content = '1.2.3.4'; + it('should be able to create a record', async() => { + const record: IRecord = TestService.getTestData().records[0]; + record.content = '1.2.3.4'; - const createdRecord: Record = await cloudflareClient.syncRecord(record); + const createdRecord: Record = await cloudflareClient.syncRecord(record); - const expectedRecordType: string = record.type ? record.type : 'A'; - expect(createdRecord.id).to.be.string; - expect(createdRecord.id.length).to.be.greaterThan(0); - expect(createdRecord.name).to.be.string; - expect(createdRecord.name.length).to.be.greaterThan(0); - expect(createdRecord.type).to.be.string; - expect(createdRecord.type).to.equal(expectedRecordType); - }); + const expectedRecordType: string = record.type ? record.type : 'A'; + expect(createdRecord.id).to.be.string; + expect(createdRecord.id.length).to.be.greaterThan(0); + expect(createdRecord.name).to.be.string; + expect(createdRecord.name.length).to.be.greaterThan(0); + expect(createdRecord.type).to.be.string; + expect(createdRecord.type).to.equal(expectedRecordType); + }); - it('should be able to get a record id', async() => { - const record: IRecord = testData.records[0]; - const recordId: string = await cloudflareClient.getRecordIdByName(record.name); + it('should be able to get a record id', async() => { + const record: IRecord = TestService.getTestData().records[0]; + record.content = '1.2.3.4'; + await cloudflareClient.syncRecord(record); - expect(recordId).to.be.string; - expect(recordId.length).to.be.greaterThan(0); - }); + const recordId: string = await cloudflareClient.getRecordIdByName(record.name); - it('should be able to remove a record', async() => { - const record: IRecord = testData.records[0]; - const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); + expect(recordId).to.be.string; + expect(recordId.length).to.be.greaterThan(0); + }); - const recordId: string = await cloudflareClient.getRecordIdByName(record.name); + it('should be able to remove a record', async() => { + const record: IRecord = TestService.getTestData().records[0]; + record.content = '1.2.3.4'; + await cloudflareClient.syncRecord(record); + const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); + const recordId: string = await cloudflareClient.getRecordIdByName(record.name); - await cloudflareClient.removeRecord(zoneId, recordId); - }); + await cloudflareClient.removeRecord(zoneId, recordId); }); it('should get record data for records', async() => { - const records: Array = testData.records; - + const records: Array = TestService.getTestData().records; for (const record of records) { await cloudflareClient.syncRecord(record, '1.2.3.4'); } @@ -71,45 +81,29 @@ describe('Cloudflare Client', () => { expect(recordData.length).to.equal(records.length); expect(recordDataNames).to.contain(records[0].name); - - for (const record of recordData) { - const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); - - await cloudflareClient.removeRecord(zoneId, record.id); - } }); it('should sync existing record', async() => { - const record: IRecord = testData.records[0]; - + const record: IRecord = TestService.getTestData().records[0]; await cloudflareClient.syncRecord(record); + const recordData: Record = await cloudflareClient.syncRecord(record); expect(recordData.name).to.equal(record.name); - - const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); - const recordId: string = await cloudflareClient.getRecordIdByName(record.name); - - await cloudflareClient.removeRecord(zoneId, recordId); }); it('should sync with ip via parameter', async() => { - const record: IRecord = testData.records[0]; + const record: IRecord = TestService.getTestData().records[0]; const randomIp: string = getRandomIp(); const recordData: Record = await cloudflareClient.syncRecord(record, randomIp); expect(recordData.name).to.equal(record.name); expect(recordData.content).to.equal(randomIp); - - const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); - const recordId: string = await cloudflareClient.getRecordIdByName(record.name); - - await cloudflareClient.removeRecord(zoneId, recordId); }); it('should sync with ip via record.content', async() => { - const record: IRecord = testData.records[0]; + const record: IRecord = TestService.getTestData().records[0]; const randomIp: string = getRandomIp(); record.content = randomIp; @@ -117,15 +111,10 @@ describe('Cloudflare Client', () => { expect(recordData.name).to.equal(record.name); expect(recordData.content).to.equal(randomIp); - - const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); - const recordId: string = await cloudflareClient.getRecordIdByName(record.name); - - await cloudflareClient.removeRecord(zoneId, recordId); }); it('should sync with external ip', async() => { - const record: IRecord = testData.records[0]; + const record: IRecord = TestService.getTestData().records[0]; record.content = undefined; const currentIp: string = await IPUtils.getIp(); @@ -133,11 +122,6 @@ describe('Cloudflare Client', () => { expect(recordData.name).to.equal(record.name); expect(recordData.content).to.equal(currentIp); - - const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); - const recordId: string = await cloudflareClient.getRecordIdByName(record.name); - - await cloudflareClient.removeRecord(zoneId, recordId); }); }); From 387b66d2dff4e49665390f5620756b8eadd5cbd8 Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 13 Sep 2019 17:22:08 +0200 Subject: [PATCH 38/70] :lipstick: Mark Prepare Parts --- src/tests/cloudflare-client-test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts index c9b2f56..561bd8e 100644 --- a/src/tests/cloudflare-client-test.ts +++ b/src/tests/cloudflare-client-test.ts @@ -32,8 +32,10 @@ describe('Cloudflare Client', () => { }); it('should be able to create a record', async() => { + // Prepare const record: IRecord = TestService.getTestData().records[0]; record.content = '1.2.3.4'; + // Prepare END const createdRecord: Record = await cloudflareClient.syncRecord(record); @@ -47,9 +49,11 @@ describe('Cloudflare Client', () => { }); it('should be able to get a record id', async() => { + // Prepare const record: IRecord = TestService.getTestData().records[0]; record.content = '1.2.3.4'; await cloudflareClient.syncRecord(record); + // Pepare END const recordId: string = await cloudflareClient.getRecordIdByName(record.name); @@ -58,20 +62,24 @@ describe('Cloudflare Client', () => { }); it('should be able to remove a record', async() => { + // Prepare const record: IRecord = TestService.getTestData().records[0]; record.content = '1.2.3.4'; await cloudflareClient.syncRecord(record); const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); const recordId: string = await cloudflareClient.getRecordIdByName(record.name); + // Prepare END await cloudflareClient.removeRecord(zoneId, recordId); }); it('should get record data for records', async() => { + // Prepare const records: Array = TestService.getTestData().records; for (const record of records) { await cloudflareClient.syncRecord(record, '1.2.3.4'); } + // Prepare END const recordData: Array = await cloudflareClient.getRecordDataForRecords(records); @@ -84,8 +92,10 @@ describe('Cloudflare Client', () => { }); it('should sync existing record', async() => { + // Prepare const record: IRecord = TestService.getTestData().records[0]; await cloudflareClient.syncRecord(record); + // Prepare END const recordData: Record = await cloudflareClient.syncRecord(record); @@ -93,8 +103,10 @@ describe('Cloudflare Client', () => { }); it('should sync with ip via parameter', async() => { + // Prepare const record: IRecord = TestService.getTestData().records[0]; const randomIp: string = getRandomIp(); + // Prepare END const recordData: Record = await cloudflareClient.syncRecord(record, randomIp); @@ -103,9 +115,11 @@ describe('Cloudflare Client', () => { }); it('should sync with ip via record.content', async() => { + // Prepare const record: IRecord = TestService.getTestData().records[0]; const randomIp: string = getRandomIp(); record.content = randomIp; + // Prepare END const recordData: Record = await cloudflareClient.syncRecord(record); @@ -114,9 +128,11 @@ describe('Cloudflare Client', () => { }); it('should sync with external ip', async() => { + // Prepare const record: IRecord = TestService.getTestData().records[0]; record.content = undefined; const currentIp: string = await IPUtils.getIp(); + // Prepare END const recordData: Record = await cloudflareClient.syncRecord(record); From 19b0ebfe2432824a9e3034a7815037c8b552c73d Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 13 Sep 2019 17:29:30 +0200 Subject: [PATCH 39/70] :lipstick: Improve Jenkins Commit Status Name --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index f0c366b..4e973d8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -17,7 +17,7 @@ def setBuildStatus(String message, String state) { step([ $class: "GitHubCommitStatusSetter", reposSource: [$class: "ManuallyEnteredRepositorySource", url: "/~https://github.com/SteffenKn/Cloudflare-DDNS-Sync"], - contextSource: [$class: "ManuallyEnteredCommitContextSource", context: "👷‍♂️ Jenkins"], + contextSource: [$class: "ManuallyEnteredCommitContextSource", context: "Jenkins"], errorHandlers: [[$class: "ChangingBuildStatusErrorHandler", result: "UNSTABLE"]], statusResultSource: [ $class: "ConditionalStatusResultSource", results: [[$class: "AnyBuildResult", message: message, state: state]] ] ]); From 3355f1c7d2185a71809bb4f4a4af37c0d44c97ca Mon Sep 17 00:00:00 2001 From: Steffen Date: Sat, 14 Sep 2019 13:39:29 +0200 Subject: [PATCH 40/70] :lipstick: Improve Npmignore --- .npmignore | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.npmignore b/.npmignore index 504afef..276ea08 100644 --- a/.npmignore +++ b/.npmignore @@ -1,2 +1,8 @@ -node_modules/ +.github/ +src/ +dist/tests + package-lock.json +tsconfig.json +tslint.json +Jenkinsfile From bbc03dcfe3a4ec227027c48971162664788b036f Mon Sep 17 00:00:00 2001 From: Steffen Date: Sat, 14 Sep 2019 13:42:52 +0200 Subject: [PATCH 41/70] :recycle: Refactor Tests --- .github/workflows/push.yml | 5 ++- Jenkinsfile | 4 +-- src/tests/cloudflare-client-test.ts | 47 ++++++++++++++++++++++---- src/tests/test-service/test-data.json | 9 +---- src/tests/test-service/test-service.ts | 46 ++++++++++++++++++------- 5 files changed, 80 insertions(+), 31 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index d4d646e..a24884e 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -13,7 +13,6 @@ jobs: runs-on: ubuntu-latest strategy: - max-parallel: 1 fail-fast: false matrix: node-version: [10.x, 12.x] @@ -37,5 +36,5 @@ jobs: env: CLOUDFLARE_EMAIL: ${{ secrets.TESTS__CLOUDFLARE_EMAIL }} CLOUDFLARE_KEY: ${{ secrets.TESTS__CLOUDFLARE_KEY }} - CLOUDFLARE_RECORDS: ${{ secrets.TESTS__CLOUDFLARE_RECORDS }} - run: npm test -- --email=$CLOUDFLARE_EMAIL --key=$CLOUDFLARE_KEY --records=$CLOUDFLARE_RECORDS + CLOUDFLARE_DOMAIN: ${{ secrets.TESTS__CLOUDFLARE_DOMAIN }} + run: npm test -- --email=$CLOUDFLARE_EMAIL --key=$CLOUDFLARE_KEY --domain=$CLOUDFLARE_DOMAIN diff --git a/Jenkinsfile b/Jenkinsfile index 4e973d8..7b1e93d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -81,8 +81,8 @@ pipeline { steps { sh('node --version') - withCredentials([string(credentialsId: 'CLOUDFLARE_EMAIL', variable: 'CLOUDFLARE_EMAIL'), string(credentialsId: 'CLOUDFLARE_KEY', variable: 'CLOUDFLARE_KEY'), string(credentialsId: 'CLOUDFLARE_RECORDS', variable: 'CLOUDFLARE_RECORDS')]) { - sh('npm run test-jenkins -- --email="'+CLOUDFLARE_EMAIL+'" --key="'+CLOUDFLARE_KEY+'" --records="'+CLOUDFLARE_RECORDS+'"') + withCredentials([string(credentialsId: 'CLOUDFLARE_EMAIL', variable: 'CLOUDFLARE_EMAIL'), string(credentialsId: 'CLOUDFLARE_KEY', variable: 'CLOUDFLARE_KEY'), string(credentialsId: 'CLOUDFLARE_DOMAIN', variable: 'CLOUDFLARE_DOMAIN')]) { + sh('npm run test-jenkins -- --email="'+CLOUDFLARE_EMAIL+'" --key="'+CLOUDFLARE_KEY+'" --domain="'+CLOUDFLARE_DOMAIN+'"') junit 'report.xml' } diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts index 561bd8e..f4ee71c 100644 --- a/src/tests/cloudflare-client-test.ts +++ b/src/tests/cloudflare-client-test.ts @@ -11,15 +11,22 @@ const expect: Chai.ExpectStatic = chai.expect; const cloudflareClient: CloudflareClient = new CloudflareClient(TestService.getTestData().auth.email, TestService.getTestData().auth.key); +const recordsToCleanUp: Array = []; + describe('Cloudflare Client', () => { afterEach(async() => { - const records: Array = TestService.getTestData().records; - const recordData: Array = await cloudflareClient.getRecordDataForRecords(records); + const recordData: Array = await cloudflareClient.getRecordDataForRecords(recordsToCleanUp); for (const record of recordData) { const zoneId: string = record.zone_id; await cloudflareClient.removeRecord(zoneId, record.id); + + const indexOfRecord: number = recordsToCleanUp.findIndex((recordToCleanup: IRecord) => { + return record.name = recordToCleanup.name; + }); + + recordsToCleanUp.splice(indexOfRecord, 1); } }); @@ -46,6 +53,10 @@ describe('Cloudflare Client', () => { expect(createdRecord.name.length).to.be.greaterThan(0); expect(createdRecord.type).to.be.string; expect(createdRecord.type).to.equal(expectedRecordType); + + // Cleanup + recordsToCleanUp.push(record); + // Cleanup END }); it('should be able to get a record id', async() => { @@ -59,6 +70,10 @@ describe('Cloudflare Client', () => { expect(recordId).to.be.string; expect(recordId.length).to.be.greaterThan(0); + + // Cleanup + recordsToCleanUp.push(record); + // Cleanup END }); it('should be able to remove a record', async() => { @@ -89,6 +104,10 @@ describe('Cloudflare Client', () => { expect(recordData.length).to.equal(records.length); expect(recordDataNames).to.contain(records[0].name); + + // Cleanup + recordsToCleanUp.push(...records); + // Cleanup END }); it('should sync existing record', async() => { @@ -99,7 +118,11 @@ describe('Cloudflare Client', () => { const recordData: Record = await cloudflareClient.syncRecord(record); - expect(recordData.name).to.equal(record.name); + expect(recordData.name.toLowerCase()).to.equal(record.name.toLowerCase()); + + // Cleanup + recordsToCleanUp.push(record); + // Cleanup END }); it('should sync with ip via parameter', async() => { @@ -110,8 +133,12 @@ describe('Cloudflare Client', () => { const recordData: Record = await cloudflareClient.syncRecord(record, randomIp); - expect(recordData.name).to.equal(record.name); + expect(recordData.name.toLowerCase()).to.equal(record.name.toLowerCase()); expect(recordData.content).to.equal(randomIp); + + // Cleanup + recordsToCleanUp.push(record); + // Cleanup END }); it('should sync with ip via record.content', async() => { @@ -123,8 +150,12 @@ describe('Cloudflare Client', () => { const recordData: Record = await cloudflareClient.syncRecord(record); - expect(recordData.name).to.equal(record.name); + expect(recordData.name.toLowerCase()).to.equal(record.name.toLowerCase()); expect(recordData.content).to.equal(randomIp); + + // Cleanup + recordsToCleanUp.push(record); + // Cleanup END }); it('should sync with external ip', async() => { @@ -136,8 +167,12 @@ describe('Cloudflare Client', () => { const recordData: Record = await cloudflareClient.syncRecord(record); - expect(recordData.name).to.equal(record.name); + expect(recordData.name.toLowerCase()).to.equal(record.name.toLowerCase()); expect(recordData.content).to.equal(currentIp); + + // Cleanup + recordsToCleanUp.push(record); + // Cleanup END }); }); diff --git a/src/tests/test-service/test-data.json b/src/tests/test-service/test-data.json index 28d486f..951a0f5 100644 --- a/src/tests/test-service/test-data.json +++ b/src/tests/test-service/test-data.json @@ -3,12 +3,5 @@ "email" : "your@email.com", "key" : "your_cloudflare_api_key" }, - "records" : [ - { - "name": "cddnstest.your-domain.com" - }, - { - "name": "cddns.test.your-domain.com" - } - ] + "domain" : "yourdomain.com" } diff --git a/src/tests/test-service/test-service.ts b/src/tests/test-service/test-service.ts index 8389e41..60783c6 100644 --- a/src/tests/test-service/test-service.ts +++ b/src/tests/test-service/test-service.ts @@ -8,24 +8,46 @@ export default class TestService { public static getTestData(): TestData { const args: ParsedArgs = minimist(process.argv.slice(2)); - const testData: TestData = Object.assign({}, testConfig); + const email: string = args.email ? args.email : testConfig.auth.email; + const key: string = args.key ? args.key : testConfig.auth.key; + const domain: string = args.domain ? args.domain : testConfig.domain; + // const records: Array = this.getRecords(args.domain ? args.domain : testConfig.domain); + + const testData: TestData = { + auth: { + email: email, + key: key, + }, + records: this.getRandomRecords(5, domain), + }; - const email: string | undefined = args.email; - const key: string | undefined = args.key; - const recordsAsString: string | undefined = args.records; + return testData; + } - testData.auth.email = email ? email : testData.auth.email; - testData.auth.key = key ? key : testData.auth.key; + private static getRandomRecords(amount: number, domain: string): Array { + const records: Array = []; - try { - const records: Array = JSON.parse(recordsAsString).records; + for (let index: number = 0; index < amount; index++) { + const record: IRecord = { + name: `cddnss-test-${this.getRandomSubdomain()}.${domain}`, + }; - testData.records = records; - } catch { - // Do nothing + records.push(record); } - return testData; + return records; + } + + private static getRandomSubdomain(): string { + let result: string = ''; + const characters: string = 'abcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength: number = characters.length; + + for ( let index: number = 0; index < 5; index++ ) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + + return result; } } From 0001a69ce998a05eed83a14ad0382f4151954031 Mon Sep 17 00:00:00 2001 From: Steffen Date: Sat, 14 Sep 2019 18:28:35 +0200 Subject: [PATCH 42/70] :sparkles: Add getRecordDataForDomain Functionality --- src/index.ts | 8 ++++++++ src/lib/cloudflare-client.ts | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/index.ts b/src/index.ts index fdcfb89..bc6f2c6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,14 @@ export default class CloudflareDDNSSync { return this.cloudflareClient.syncRecord(record); } + public async getRecordDataForDomain(domain: string): Promise> { + return this.cloudflareClient.getRecordDataForDomain(domain); + } + + public async getRecordDataForDomains(domains: Array): Promise> { + return this.cloudflareClient.getRecordDataForDomains(domains); + } + public async removeRecord(recordName: string): Promise { const zoneId: string = await this.cloudflareClient.getZoneIdByRecordName(recordName); const recordId: string = await this.cloudflareClient.getRecordIdByName(recordName); diff --git a/src/lib/cloudflare-client.ts b/src/lib/cloudflare-client.ts index 1f7fefd..06c063f 100644 --- a/src/lib/cloudflare-client.ts +++ b/src/lib/cloudflare-client.ts @@ -122,6 +122,30 @@ export default class CloudflareClient { return recordData; } + // TODO: Performance? + public async getRecordDataForDomains(domains: Array): Promise> { + // console.time('getRecordDataForDomains'); + + const recordDataPromises: Array>> = domains.map(async(domain: string) => { + return this.getRecordDataForDomain(domain); + }); + + const recordDataForDomains: Array> = await Promise.all(recordDataPromises); + const recordData: Array = [].concat(...recordDataForDomains); + + // console.timeEnd('getRecordDataForDomains'); + return recordData; + } + + // TODO: Performance? + public async getRecordDataForDomain(domain: string): Promise> { + // console.time('getRecordDataForDomain'); + const recordData: Array = await this.getRecordsByDomain(domain); + // console.timeEnd('getRecordDataForDomain'); + + return recordData; + } + private async createRecord(zoneId: string, record: IRecord, ip?: string): Promise { const copyOfRecord: IRecord = Object.assign({}, record); copyOfRecord.content = copyOfRecord.content ? copyOfRecord.content : ip; From 39c280460a823d916bd0a054e823b1a9557d32b0 Mon Sep 17 00:00:00 2001 From: Steffen Date: Sat, 14 Sep 2019 19:19:30 +0200 Subject: [PATCH 43/70] :recycle: Refactor Record Types --- src/contracts/Callbacks.ts | 6 +- src/contracts/{IRecord.ts => Record.ts} | 4 +- src/contracts/SyncResult.ts | 6 +- .../cloudflare/{Record.ts => RecordData.ts} | 2 +- .../cloudflare/{Zone.ts => ZoneData.ts} | 2 +- src/contracts/cloudflare/index.ts | 4 +- src/contracts/index.ts | 3 +- src/index.ts | 25 ++--- src/lib/cloudflare-client.ts | 92 ++++++++++--------- src/tests/cloudflare-client-test.ts | 47 +++++----- src/tests/test-service/test-service.ts | 12 ++- 11 files changed, 107 insertions(+), 96 deletions(-) rename src/contracts/{IRecord.ts => Record.ts} (79%) rename src/contracts/cloudflare/{Record.ts => RecordData.ts} (93%) rename src/contracts/cloudflare/{Zone.ts => ZoneData.ts} (97%) diff --git a/src/contracts/Callbacks.ts b/src/contracts/Callbacks.ts index 98fd953..cdb000f 100644 --- a/src/contracts/Callbacks.ts +++ b/src/contracts/Callbacks.ts @@ -1,4 +1,4 @@ -import {Record} from './index'; +import {RecordData} from './index'; -export type SingleSyncCallback = (syncResult: Record) => void; -export type MultiSyncCallback = (syncResult: Array) => void; +export type SingleSyncCallback = (syncResult: RecordData) => void; +export type MultiSyncCallback = (syncResult: Array) => void; diff --git a/src/contracts/IRecord.ts b/src/contracts/Record.ts similarity index 79% rename from src/contracts/IRecord.ts rename to src/contracts/Record.ts index b2e7659..89b8e86 100644 --- a/src/contracts/IRecord.ts +++ b/src/contracts/Record.ts @@ -1,8 +1,8 @@ -export interface IRecord { +export type Record = { name: string; type?: string; proxied?: boolean; ttl?: number; priority?: number; content?: string; -} +}; diff --git a/src/contracts/SyncResult.ts b/src/contracts/SyncResult.ts index bef3edf..27a38ee 100644 --- a/src/contracts/SyncResult.ts +++ b/src/contracts/SyncResult.ts @@ -1,4 +1,4 @@ -import {Record} from './index'; +import {RecordData} from './index'; -export type SingleSyncResult = Promise; -export type MultiSyncResult = Promise>; +export type SingleSyncResult = Promise; +export type MultiSyncResult = Promise>; diff --git a/src/contracts/cloudflare/Record.ts b/src/contracts/cloudflare/RecordData.ts similarity index 93% rename from src/contracts/cloudflare/Record.ts rename to src/contracts/cloudflare/RecordData.ts index e64772f..f25085a 100644 --- a/src/contracts/cloudflare/Record.ts +++ b/src/contracts/cloudflare/RecordData.ts @@ -1,4 +1,4 @@ -export type Record = { +export type RecordData = { id: string; type: string; name: string; diff --git a/src/contracts/cloudflare/Zone.ts b/src/contracts/cloudflare/ZoneData.ts similarity index 97% rename from src/contracts/cloudflare/Zone.ts rename to src/contracts/cloudflare/ZoneData.ts index 5723057..fa343bf 100644 --- a/src/contracts/cloudflare/Zone.ts +++ b/src/contracts/cloudflare/ZoneData.ts @@ -1,4 +1,4 @@ -export type Zone = { +export type ZoneData = { id: string; name: string; status: string; diff --git a/src/contracts/cloudflare/index.ts b/src/contracts/cloudflare/index.ts index ba8eabc..1238871 100644 --- a/src/contracts/cloudflare/index.ts +++ b/src/contracts/cloudflare/index.ts @@ -1,2 +1,2 @@ -export * from './Record'; -export * from './Zone'; +export * from './RecordData'; +export * from './ZoneData'; diff --git a/src/contracts/index.ts b/src/contracts/index.ts index f72106b..2e9a1d9 100644 --- a/src/contracts/index.ts +++ b/src/contracts/index.ts @@ -1,6 +1,7 @@ export * from './cloudflare/index'; export * from './Callbacks'; -export * from './IRecord'; +export * from './DomainRecordList'; +export * from './Record'; export * from './SyncResult'; export * from './ZoneMap'; diff --git a/src/index.ts b/src/index.ts index bc6f2c6..512d190 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,10 +5,11 @@ import Cron from './lib/cron'; import ipUtils from './lib/ip-utils'; import { - IRecord, + DomainRecordList, MultiSyncCallback, MultiSyncResult, Record, + RecordData, SingleSyncResult, } from './contracts/index'; @@ -19,7 +20,7 @@ export default class CloudflareDDNSSync { this.cloudflareClient = new CloudflareClient(email, authKey); } - public getRecordDataForRecords(records: Array): Promise> { + public getRecordDataForRecords(records: Array): Promise> { return this.cloudflareClient.getRecordDataForRecords(records); } @@ -27,15 +28,15 @@ export default class CloudflareDDNSSync { return ipUtils.getIp(); } - public async createRecord(record: IRecord): Promise { + public async createRecord(record: Record): Promise { return this.cloudflareClient.syncRecord(record); } - public async getRecordDataForDomain(domain: string): Promise> { + public async getRecordDataForDomain(domain: string): Promise> { return this.cloudflareClient.getRecordDataForDomain(domain); } - public async getRecordDataForDomains(domains: Array): Promise> { + public async getRecordDataForDomains(domains: Array): Promise { return this.cloudflareClient.getRecordDataForDomains(domains); } @@ -46,14 +47,14 @@ export default class CloudflareDDNSSync { this.cloudflareClient.removeRecord(zoneId, recordId); } - public async sync(record: IRecord, ip?: string): SingleSyncResult { + public async sync(record: Record, ip?: string): SingleSyncResult { const ipToUse: string = ip ? ip : await ipUtils.getIp(); return this.cloudflareClient.syncRecord(record, ipToUse); } - public async syncRecords(records: Array, ip?: string): MultiSyncResult { + public async syncRecords(records: Array, ip?: string): MultiSyncResult { const currentIp: string = await ipUtils.getIp(); const ipToUse: string = ip ? ip : currentIp; @@ -61,15 +62,15 @@ export default class CloudflareDDNSSync { return this.cloudflareClient.syncRecords(records, ipToUse); } - public async syncOnIpChange(records: Array, callback: MultiSyncCallback): Promise { + public async syncOnIpChange(records: Array, callback: MultiSyncCallback): Promise { const changeListenerId: string = await ipUtils.addIpChangeListener(async(ip: string) => { - const result: Array = await this.syncRecords(records, ip); + const result: Array = await this.syncRecords(records, ip); callback(result); }); const currentIp: string = await ipUtils.getIp(); - this.syncRecords(records, currentIp).then((syncedRecords: Array) => { + this.syncRecords(records, currentIp).then((syncedRecords: Array): void => { callback(syncedRecords); }); @@ -80,9 +81,9 @@ export default class CloudflareDDNSSync { ipUtils.removeIpChangeListener(changeListenerId); } - public syncByCronTime(cronExpression: string, records: Array, callback: MultiSyncCallback, ip?: string): ScheduledTask { + public syncByCronTime(cronExpression: string, records: Array, callback: MultiSyncCallback, ip?: string): ScheduledTask { return Cron.createCronJob(cronExpression, async() => { - const result: Array = await this.syncRecords(records, ip); + const result: Array = await this.syncRecords(records, ip); callback(result); }); diff --git a/src/lib/cloudflare-client.ts b/src/lib/cloudflare-client.ts index 06c063f..61a7f96 100644 --- a/src/lib/cloudflare-client.ts +++ b/src/lib/cloudflare-client.ts @@ -1,7 +1,7 @@ import Cloudflare from 'cloudflare'; import parseDomain from 'parse-domain'; -import {IRecord, Record, Zone, ZoneMap} from '../contracts'; +import {DomainRecordList, Record, RecordData, ZoneData, ZoneMap} from '../contracts'; import IPUtils from './ip-utils'; export default class CloudflareClient { @@ -18,7 +18,7 @@ export default class CloudflareClient { this.updateZoneMap(); } - public async syncRecord(record: IRecord, ip?: string): Promise { + public async syncRecord(record: Record, ip?: string): Promise { const recordIds: Map = await this.getRecordIdsForRecords([record]); const ipToUse: string = ip ? ip : await IPUtils.getIp(); @@ -27,38 +27,38 @@ export default class CloudflareClient { const recordExists: boolean = recordId !== undefined; if (recordExists) { - const result: Record = await this.updateRecord(zoneId, recordId, record, ipToUse); + const result: RecordData = await this.updateRecord(zoneId, recordId, record, ipToUse); return result; } else { - const result: Record = await this.createRecord(zoneId, record, ipToUse); + const result: RecordData = await this.createRecord(zoneId, record, ipToUse); return result; } } - public async syncRecords(records: Array, ip?: string): Promise> { + public async syncRecords(records: Array, ip?: string): Promise> { const recordIds: Map = await this.getRecordIdsForRecords(records); const ipToUse: string = ip ? ip : await IPUtils.getIp(); - const resultPromises: Array> = records.map(async(record: IRecord) => { + const resultPromises: Array> = records.map(async(record: Record) => { const zoneId: string = await this.getZoneIdByRecordName(record.name); const recordId: string = recordIds.get(record.name); const recordExists: boolean = recordId !== undefined; if (recordExists) { - const currentResult: Record = await this.updateRecord(zoneId, recordId, record, ipToUse); + const currentResult: RecordData = await this.updateRecord(zoneId, recordId, record, ipToUse); return currentResult; } else { - const currentResult: Record = await this.createRecord(zoneId, record, ipToUse); + const currentResult: RecordData = await this.createRecord(zoneId, record, ipToUse); return currentResult; } }); - const results: Array = await Promise.all(resultPromises); + const results: Array = await Promise.all(resultPromises); return results; } @@ -78,19 +78,19 @@ export default class CloudflareClient { } public async getRecordIdByName(recordName: string): Promise { - const record: Record = await this.getRecordByName(recordName); + const record: RecordData = await this.getRecordByName(recordName); return record.id; } // TODO: Performance? - public async getRecordDataForRecord(record: IRecord): Promise { + public async getRecordDataForRecord(record: Record): Promise { // console.time('getRecordDataForRecord'); const domain: string = this.getDomainByRecordName(record.name); - const recordDataForDomain: Array = await this.getRecordsByDomain(domain); + const recordDataForDomain: Array = await this.getRecordsByDomain(domain); - const recordData: Record = recordDataForDomain.find((singleRecordData: Record) => { + const recordData: RecordData = recordDataForDomain.find((singleRecordData: RecordData) => { return record.name === singleRecordData.name; }); @@ -99,15 +99,15 @@ export default class CloudflareClient { } // TODO: Performance? - public async getRecordDataForRecords(records: Array): Promise> { + public async getRecordDataForRecords(records: Array): Promise> { // console.time('getRecordDataForRecords'); const domains: Array = this.getDomainsFromRecords(records); - const recordDataPromises: Array>> = domains.map(async(domain: string) => { - const recordDataForDomain: Array = await this.getRecordsByDomain(domain); + const recordDataPromises: Array>> = domains.map(async(domain: string) => { + const recordDataForDomain: Array = await this.getRecordsByDomain(domain); - const recordDataForDomainFilteredByRecords: Array = recordDataForDomain.filter((singleRecordData: Record) => { - return records.some((record: IRecord) => { + const recordDataForDomainFilteredByRecords: Array = recordDataForDomain.filter((singleRecordData: RecordData) => { + return records.some((record: Record) => { return record.name === singleRecordData.name; }); }); @@ -115,39 +115,43 @@ export default class CloudflareClient { return recordDataForDomainFilteredByRecords; }); - const recordDataForDomains: Array> = await Promise.all(recordDataPromises); - const recordData: Array = [].concat(...recordDataForDomains); + const recordDataForDomains: Array> = await Promise.all(recordDataPromises); + const recordData: Array = [].concat(...recordDataForDomains); // console.timeEnd('getRecordDataForRecords'); return recordData; } // TODO: Performance? - public async getRecordDataForDomains(domains: Array): Promise> { + public async getRecordDataForDomains(domains: Array): Promise { // console.time('getRecordDataForDomains'); - const recordDataPromises: Array>> = domains.map(async(domain: string) => { + const recordDataPromises: Array>> = domains.map(async(domain: string) => { return this.getRecordDataForDomain(domain); }); - const recordDataForDomains: Array> = await Promise.all(recordDataPromises); - const recordData: Array = [].concat(...recordDataForDomains); + const recordDataForDomains: Array> = await Promise.all(recordDataPromises); + + const recordData: DomainRecordList = {}; + recordDataForDomains.forEach((recordDataForDomain: Array, index: number): void => { + recordData[domains[index]] = recordDataForDomain; + }); // console.timeEnd('getRecordDataForDomains'); return recordData; } // TODO: Performance? - public async getRecordDataForDomain(domain: string): Promise> { + public async getRecordDataForDomain(domain: string): Promise> { // console.time('getRecordDataForDomain'); - const recordData: Array = await this.getRecordsByDomain(domain); + const recordData: Array = await this.getRecordsByDomain(domain); // console.timeEnd('getRecordDataForDomain'); return recordData; } - private async createRecord(zoneId: string, record: IRecord, ip?: string): Promise { - const copyOfRecord: IRecord = Object.assign({}, record); + private async createRecord(zoneId: string, record: Record, ip?: string): Promise { + const copyOfRecord: Record = Object.assign({}, record); copyOfRecord.content = copyOfRecord.content ? copyOfRecord.content : ip; copyOfRecord.type = copyOfRecord.type ? copyOfRecord.type : 'A'; @@ -156,7 +160,7 @@ export default class CloudflareClient { } try { - const response: {result: Record} = await this.cloudflare.dnsRecords.add(zoneId, copyOfRecord); + const response: {result: RecordData} = await this.cloudflare.dnsRecords.add(zoneId, copyOfRecord); return response.result; } catch (error) { @@ -164,8 +168,8 @@ export default class CloudflareClient { } } - private async updateRecord(zoneId: string, recordId: string, record: IRecord, ip?: string): Promise { - const copyOfRecord: IRecord = Object.assign({}, record); + private async updateRecord(zoneId: string, recordId: string, record: Record, ip?: string): Promise { + const copyOfRecord: Record = Object.assign({}, record); copyOfRecord.content = copyOfRecord.content ? copyOfRecord.content : ip; copyOfRecord.type = copyOfRecord.type ? copyOfRecord.type : 'A'; @@ -174,7 +178,7 @@ export default class CloudflareClient { } try { - const response: {result: Record} = await this.cloudflare.dnsRecords.edit(zoneId, recordId, copyOfRecord); + const response: {result: RecordData} = await this.cloudflare.dnsRecords.edit(zoneId, recordId, copyOfRecord); return response.result; } catch (error) { @@ -183,8 +187,8 @@ export default class CloudflareClient { } private async updateZoneMap(): Promise { - const response: {result: Array} = await this.cloudflare.zones.browse(); - const zones: Array = response.result; + const response: {result: Array} = await this.cloudflare.zones.browse(); + const zones: Array = response.result; this.zoneMap = new Map(); for (const zone of zones) { @@ -192,12 +196,12 @@ export default class CloudflareClient { } } - private async getRecordByName(recordName: string): Promise { + private async getRecordByName(recordName: string): Promise { const domain: string = this.getDomainByRecordName(recordName); - const records: Array = await this.getRecordsByDomain(domain); + const records: Array = await this.getRecordsByDomain(domain); - const record: Record = records.find((currentRecord: Record) => { + const record: RecordData = records.find((currentRecord: RecordData) => { return currentRecord.name === recordName; }); @@ -210,12 +214,12 @@ export default class CloudflareClient { } // TODO: Performance? - private async getRecordIdsForRecords(records: Array): Promise> { + private async getRecordIdsForRecords(records: Array): Promise> { // console.time('getRecordIdsToUpdate'); const recordIdMap: Map = new Map(); - const recordData: Array = await this.getRecordDataForRecords(records); + const recordData: Array = await this.getRecordDataForRecords(records); for (const record of recordData) { recordIdMap.set(record.name, record.id); @@ -226,11 +230,11 @@ export default class CloudflareClient { return recordIdMap; } - private async getRecordsByDomain(domain: string): Promise> { + private async getRecordsByDomain(domain: string): Promise> { const zoneId: string = await this.getZoneIdByDomain(domain); - const response: {result: Array} = await this.cloudflare.dnsRecords.browse(zoneId); - const records: Array = response.result; + const response: {result: Array} = await this.cloudflare.dnsRecords.browse(zoneId); + const records: Array = response.result; return records; } @@ -248,8 +252,8 @@ export default class CloudflareClient { } } - private getDomainsFromRecords(records: Array): Array { - const domains: Array = records.map((record: IRecord) => { + private getDomainsFromRecords(records: Array): Array { + const domains: Array = records.map((record: Record) => { return this.getDomainByRecordName(record.name); }).filter((domain: string, index: number, domainList: Array) => { return domainList.indexOf(domain) === index; diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts index f4ee71c..7fc3191 100644 --- a/src/tests/cloudflare-client-test.ts +++ b/src/tests/cloudflare-client-test.ts @@ -3,26 +3,26 @@ import chai from 'chai'; import CloudflareClient from '../lib/cloudflare-client'; import IPUtils from '../lib/ip-utils'; -import TestService from './test-service/test-service'; +import TestService, { TestData } from './test-service/test-service'; -import {IRecord, Record} from '../contracts'; +import {Record, RecordData} from '../contracts'; const expect: Chai.ExpectStatic = chai.expect; const cloudflareClient: CloudflareClient = new CloudflareClient(TestService.getTestData().auth.email, TestService.getTestData().auth.key); -const recordsToCleanUp: Array = []; +const recordsToCleanUp: Array = []; describe('Cloudflare Client', () => { afterEach(async() => { - const recordData: Array = await cloudflareClient.getRecordDataForRecords(recordsToCleanUp); + const recordData: Array = await cloudflareClient.getRecordDataForRecords(recordsToCleanUp); for (const record of recordData) { const zoneId: string = record.zone_id; await cloudflareClient.removeRecord(zoneId, record.id); - const indexOfRecord: number = recordsToCleanUp.findIndex((recordToCleanup: IRecord) => { + const indexOfRecord: number = recordsToCleanUp.findIndex((recordToCleanup: Record) => { return record.name = recordToCleanup.name; }); @@ -31,7 +31,7 @@ describe('Cloudflare Client', () => { }); it('should get the zone id of a domain', async() => { - const record: IRecord = TestService.getTestData().records[0]; + const record: Record = TestService.getTestData().records[0]; const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); expect(zoneId).to.be.string; @@ -40,11 +40,11 @@ describe('Cloudflare Client', () => { it('should be able to create a record', async() => { // Prepare - const record: IRecord = TestService.getTestData().records[0]; + const record: Record = TestService.getTestData().records[0]; record.content = '1.2.3.4'; // Prepare END - const createdRecord: Record = await cloudflareClient.syncRecord(record); + const createdRecord: RecordData = await cloudflareClient.syncRecord(record); const expectedRecordType: string = record.type ? record.type : 'A'; expect(createdRecord.id).to.be.string; @@ -61,7 +61,7 @@ describe('Cloudflare Client', () => { it('should be able to get a record id', async() => { // Prepare - const record: IRecord = TestService.getTestData().records[0]; + const record: Record = TestService.getTestData().records[0]; record.content = '1.2.3.4'; await cloudflareClient.syncRecord(record); // Pepare END @@ -78,7 +78,7 @@ describe('Cloudflare Client', () => { it('should be able to remove a record', async() => { // Prepare - const record: IRecord = TestService.getTestData().records[0]; + const record: Record = TestService.getTestData().records[0]; record.content = '1.2.3.4'; await cloudflareClient.syncRecord(record); const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); @@ -90,20 +90,23 @@ describe('Cloudflare Client', () => { it('should get record data for records', async() => { // Prepare - const records: Array = TestService.getTestData().records; + const records: Array = TestService.getTestData().records; for (const record of records) { await cloudflareClient.syncRecord(record, '1.2.3.4'); } // Prepare END - const recordData: Array = await cloudflareClient.getRecordDataForRecords(records); + const recordData: Array = await cloudflareClient.getRecordDataForRecords(records); - const recordDataNames: Array = recordData.map((recordDataEntry: Record) => { + const recordDataNames: Array = recordData.map((recordDataEntry: RecordData) => { return recordDataEntry.name; }); expect(recordData.length).to.equal(records.length); - expect(recordDataNames).to.contain(records[0].name); + + for (const record of records) { + expect(recordDataNames).to.contain(record.name); + } // Cleanup recordsToCleanUp.push(...records); @@ -112,11 +115,11 @@ describe('Cloudflare Client', () => { it('should sync existing record', async() => { // Prepare - const record: IRecord = TestService.getTestData().records[0]; + const record: Record = TestService.getTestData().records[0]; await cloudflareClient.syncRecord(record); // Prepare END - const recordData: Record = await cloudflareClient.syncRecord(record); + const recordData: RecordData = await cloudflareClient.syncRecord(record); expect(recordData.name.toLowerCase()).to.equal(record.name.toLowerCase()); @@ -127,11 +130,11 @@ describe('Cloudflare Client', () => { it('should sync with ip via parameter', async() => { // Prepare - const record: IRecord = TestService.getTestData().records[0]; + const record: Record = TestService.getTestData().records[0]; const randomIp: string = getRandomIp(); // Prepare END - const recordData: Record = await cloudflareClient.syncRecord(record, randomIp); + const recordData: RecordData = await cloudflareClient.syncRecord(record, randomIp); expect(recordData.name.toLowerCase()).to.equal(record.name.toLowerCase()); expect(recordData.content).to.equal(randomIp); @@ -143,12 +146,12 @@ describe('Cloudflare Client', () => { it('should sync with ip via record.content', async() => { // Prepare - const record: IRecord = TestService.getTestData().records[0]; + const record: Record = TestService.getTestData().records[0]; const randomIp: string = getRandomIp(); record.content = randomIp; // Prepare END - const recordData: Record = await cloudflareClient.syncRecord(record); + const recordData: RecordData = await cloudflareClient.syncRecord(record); expect(recordData.name.toLowerCase()).to.equal(record.name.toLowerCase()); expect(recordData.content).to.equal(randomIp); @@ -160,12 +163,12 @@ describe('Cloudflare Client', () => { it('should sync with external ip', async() => { // Prepare - const record: IRecord = TestService.getTestData().records[0]; + const record: Record = TestService.getTestData().records[0]; record.content = undefined; const currentIp: string = await IPUtils.getIp(); // Prepare END - const recordData: Record = await cloudflareClient.syncRecord(record); + const recordData: RecordData = await cloudflareClient.syncRecord(record); expect(recordData.name.toLowerCase()).to.equal(record.name.toLowerCase()); expect(recordData.content).to.equal(currentIp); diff --git a/src/tests/test-service/test-service.ts b/src/tests/test-service/test-service.ts index 60783c6..139b8c5 100644 --- a/src/tests/test-service/test-service.ts +++ b/src/tests/test-service/test-service.ts @@ -2,7 +2,7 @@ import minimist, {ParsedArgs} from 'minimist'; import testConfig from './test-data.json'; -import {IRecord} from '../../contracts/index.js'; +import {Record} from '../../contracts/index.js'; export default class TestService { public static getTestData(): TestData { @@ -18,17 +18,18 @@ export default class TestService { email: email, key: key, }, + domain: domain, records: this.getRandomRecords(5, domain), }; return testData; } - private static getRandomRecords(amount: number, domain: string): Array { - const records: Array = []; + private static getRandomRecords(amount: number, domain: string): Array { + const records: Array = []; for (let index: number = 0; index < amount; index++) { - const record: IRecord = { + const record: Record = { name: `cddnss-test-${this.getRandomSubdomain()}.${domain}`, }; @@ -56,5 +57,6 @@ export type TestData = { email: string, key: string, }, - records: Array, + domain: string, + records: Array, }; From 9d5cc93db62aa71324996d0c37fe5663f8ac35f7 Mon Sep 17 00:00:00 2001 From: Steffen Date: Sat, 14 Sep 2019 19:41:13 +0200 Subject: [PATCH 44/70] :white_check_mark: Add Tests for getRecordDataForDomain Functionality --- src/contracts/DomainRecordList.ts | 3 ++ src/tests/cloudflare-client-test.ts | 54 ++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 src/contracts/DomainRecordList.ts diff --git a/src/contracts/DomainRecordList.ts b/src/contracts/DomainRecordList.ts new file mode 100644 index 0000000..0d48a41 --- /dev/null +++ b/src/contracts/DomainRecordList.ts @@ -0,0 +1,3 @@ +import {RecordData} from './index'; + +export type DomainRecordList = {[domain: string]: Array}; diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts index 7fc3191..211aa91 100644 --- a/src/tests/cloudflare-client-test.ts +++ b/src/tests/cloudflare-client-test.ts @@ -5,7 +5,7 @@ import CloudflareClient from '../lib/cloudflare-client'; import IPUtils from '../lib/ip-utils'; import TestService, { TestData } from './test-service/test-service'; -import {Record, RecordData} from '../contracts'; +import {DomainRecordList, Record, RecordData} from '../contracts'; const expect: Chai.ExpectStatic = chai.expect; @@ -177,6 +177,58 @@ describe('Cloudflare Client', () => { recordsToCleanUp.push(record); // Cleanup END }); + + it ('should get record data for domain', async() => { + // Prepare + const testData: TestData = TestService.getTestData(); + const domain: string = testData.domain; + const records: Array = testData.records; + await cloudflareClient.syncRecords(records, '1.2.3.4'); + // Prepare END + + const recordData: Array = await cloudflareClient.getRecordDataForDomain(domain); + + const recordDataNames: Array = recordData.map((recordDataEntry: RecordData) => { + return recordDataEntry.name.toLowerCase(); + }); + + // At least the data of the synced records should be existing + expect(recordData.length).to.be.greaterThan(records.length - 1); + for (const record of records) { + expect(recordDataNames).to.contain(record.name.toLowerCase()); + } + + // Cleanup + recordsToCleanUp.push(...records); + // Cleanup END + }); + + it ('should get record data for multiple domains', async() => { + // Prepare + const testData: TestData = TestService.getTestData(); + const domain: string = testData.domain; + const records: Array = testData.records; + await cloudflareClient.syncRecords(records, '1.2.3.4'); + // Prepare END + + const domainRecordList: DomainRecordList = await cloudflareClient.getRecordDataForDomains([domain]); + + expect(Object.keys(domainRecordList)).to.contain(domain); + + const recordDataNames: Array = domainRecordList[domain].map((recordDataEntry: RecordData) => { + return recordDataEntry.name.toLowerCase(); + }); + + // At least the data of the synced records should be existing + expect(domainRecordList[domain].length).to.be.greaterThan(records.length - 1); + for (const record of records) { + expect(recordDataNames).to.contain(record.name.toLowerCase()); + } + + // Cleanup + recordsToCleanUp.push(...records); + // Cleanup END + }); }); function getRandomIp(): string { From 2b4c3cb543ecc1e630f39fd02ed8877bb6aae74b Mon Sep 17 00:00:00 2001 From: Steffen Date: Sat, 14 Sep 2019 19:42:16 +0200 Subject: [PATCH 45/70] :recycle: Use syncRecords Instead of Loop --- src/tests/cloudflare-client-test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts index 211aa91..70639d3 100644 --- a/src/tests/cloudflare-client-test.ts +++ b/src/tests/cloudflare-client-test.ts @@ -91,9 +91,7 @@ describe('Cloudflare Client', () => { it('should get record data for records', async() => { // Prepare const records: Array = TestService.getTestData().records; - for (const record of records) { - await cloudflareClient.syncRecord(record, '1.2.3.4'); - } + await cloudflareClient.syncRecords(records, '1.2.3.4'); // Prepare END const recordData: Array = await cloudflareClient.getRecordDataForRecords(records); From 9ca3be1c2fed6b791c4f15413b72ce2ab9a45fdf Mon Sep 17 00:00:00 2001 From: Steffen Date: Sat, 14 Sep 2019 19:42:41 +0200 Subject: [PATCH 46/70] :white_check_mark: Add Tests for Syncing Multiple Records --- src/tests/cloudflare-client-test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts index 70639d3..7786c9b 100644 --- a/src/tests/cloudflare-client-test.ts +++ b/src/tests/cloudflare-client-test.ts @@ -126,6 +126,26 @@ describe('Cloudflare Client', () => { // Cleanup END }); + it('should sync multiple records', async() => { + // Prepare + const records: Array = TestService.getTestData().records; + // Prepare END + + const recordData: Array = await cloudflareClient.syncRecords(records); + + const recordDataNames: Array = recordData.map((singleRecordData: RecordData) => { + return singleRecordData.name.toLowerCase(); + }); + + for (const record of records) { + expect(recordDataNames).to.contain(record.name.toLowerCase()); + } + + // Cleanup + recordsToCleanUp.push(...records); + // Cleanup END + }); + it('should sync with ip via parameter', async() => { // Prepare const record: Record = TestService.getTestData().records[0]; From 88b59951e128435dea8f2f094ccfa988c2760311 Mon Sep 17 00:00:00 2001 From: Steffen Date: Sun, 15 Sep 2019 19:00:44 +0200 Subject: [PATCH 47/70] :bug: Fix Getting All RecordData for Zone --- src/lib/cloudflare-client.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/lib/cloudflare-client.ts b/src/lib/cloudflare-client.ts index 61a7f96..965abb8 100644 --- a/src/lib/cloudflare-client.ts +++ b/src/lib/cloudflare-client.ts @@ -233,8 +233,22 @@ export default class CloudflareClient { private async getRecordsByDomain(domain: string): Promise> { const zoneId: string = await this.getZoneIdByDomain(domain); - const response: {result: Array} = await this.cloudflare.dnsRecords.browse(zoneId); - const records: Array = response.result; + const records: Array = []; + + let pageIndex: number = 1; + let allRecordsFound: boolean = false; + while (!allRecordsFound) { + const response: Response & {result: Array} = await this.cloudflare.dnsRecords.browse(zoneId, { + page: pageIndex, + per_page: 100, + }); + + records.push(...response.result); + + allRecordsFound = response.result.length < 100; + + pageIndex++; + } return records; } From 52bdf7169332a0bdfbb9b0b029f5da25b879fd80 Mon Sep 17 00:00:00 2001 From: Steffen Date: Sun, 15 Sep 2019 19:14:47 +0200 Subject: [PATCH 48/70] :bug: Fix Handling Uppercase Characters in Record Name --- src/lib/cloudflare-client.ts | 28 ++++++++++++++------------ src/tests/cloudflare-client-test.ts | 6 +++--- src/tests/test-service/test-service.ts | 4 ++-- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/lib/cloudflare-client.ts b/src/lib/cloudflare-client.ts index 965abb8..ef3fa34 100644 --- a/src/lib/cloudflare-client.ts +++ b/src/lib/cloudflare-client.ts @@ -23,7 +23,7 @@ export default class CloudflareClient { const ipToUse: string = ip ? ip : await IPUtils.getIp(); const zoneId: string = await this.getZoneIdByRecordName(record.name); - const recordId: string = recordIds.get(record.name); + const recordId: string = recordIds.get(record.name.toLowerCase()); const recordExists: boolean = recordId !== undefined; if (recordExists) { @@ -43,7 +43,7 @@ export default class CloudflareClient { const resultPromises: Array> = records.map(async(record: Record) => { const zoneId: string = await this.getZoneIdByRecordName(record.name); - const recordId: string = recordIds.get(record.name); + const recordId: string = recordIds.get(record.name.toLowerCase()); const recordExists: boolean = recordId !== undefined; if (recordExists) { @@ -91,7 +91,7 @@ export default class CloudflareClient { const recordDataForDomain: Array = await this.getRecordsByDomain(domain); const recordData: RecordData = recordDataForDomain.find((singleRecordData: RecordData) => { - return record.name === singleRecordData.name; + return record.name.toLowerCase() === singleRecordData.name.toLowerCase(); }); // console.timeEnd('getRecordDataForRecord'); @@ -108,7 +108,7 @@ export default class CloudflareClient { const recordDataForDomainFilteredByRecords: Array = recordDataForDomain.filter((singleRecordData: RecordData) => { return records.some((record: Record) => { - return record.name === singleRecordData.name; + return record.name.toLowerCase() === singleRecordData.name.toLowerCase(); }); }); @@ -152,15 +152,16 @@ export default class CloudflareClient { private async createRecord(zoneId: string, record: Record, ip?: string): Promise { const copyOfRecord: Record = Object.assign({}, record); + copyOfRecord.name = record.name.toLowerCase(); copyOfRecord.content = copyOfRecord.content ? copyOfRecord.content : ip; copyOfRecord.type = copyOfRecord.type ? copyOfRecord.type : 'A'; if (!copyOfRecord.content) { - throw Error(`Could not create Record "${record.name}": Ip is missing!`); + throw Error(`Could not create Record "${copyOfRecord.name}": Ip is missing!`); } try { - const response: {result: RecordData} = await this.cloudflare.dnsRecords.add(zoneId, copyOfRecord); + const response: Response & {result: RecordData} = await this.cloudflare.dnsRecords.add(zoneId, copyOfRecord); return response.result; } catch (error) { @@ -170,6 +171,7 @@ export default class CloudflareClient { private async updateRecord(zoneId: string, recordId: string, record: Record, ip?: string): Promise { const copyOfRecord: Record = Object.assign({}, record); + copyOfRecord.name = record.name.toLowerCase(); copyOfRecord.content = copyOfRecord.content ? copyOfRecord.content : ip; copyOfRecord.type = copyOfRecord.type ? copyOfRecord.type : 'A'; @@ -178,7 +180,7 @@ export default class CloudflareClient { } try { - const response: {result: RecordData} = await this.cloudflare.dnsRecords.edit(zoneId, recordId, copyOfRecord); + const response: Response & {result: RecordData} = await this.cloudflare.dnsRecords.edit(zoneId, recordId, copyOfRecord); return response.result; } catch (error) { @@ -202,7 +204,7 @@ export default class CloudflareClient { const records: Array = await this.getRecordsByDomain(domain); const record: RecordData = records.find((currentRecord: RecordData) => { - return currentRecord.name === recordName; + return currentRecord.name.toLowerCase() === recordName.toLowerCase(); }); const recordNotFound: boolean = record === undefined; @@ -222,7 +224,7 @@ export default class CloudflareClient { const recordData: Array = await this.getRecordDataForRecords(records); for (const record of recordData) { - recordIdMap.set(record.name, record.id); + recordIdMap.set(record.name.toLowerCase(), record.id); } // console.timeEnd('getRecordIdsToUpdate'); @@ -255,12 +257,12 @@ export default class CloudflareClient { private async getZoneIdByDomain(domain: string): Promise { if (this.zoneMap.has(domain)) { - const zoneId: string = this.zoneMap.get(domain); + const zoneId: string = this.zoneMap.get(domain.toLowerCase()); return zoneId; } else { await this.updateZoneMap(); - const zoneId: string = this.zoneMap.get(domain); + const zoneId: string = this.zoneMap.get(domain.toLowerCase()); return zoneId; } @@ -270,7 +272,7 @@ export default class CloudflareClient { const domains: Array = records.map((record: Record) => { return this.getDomainByRecordName(record.name); }).filter((domain: string, index: number, domainList: Array) => { - return domainList.indexOf(domain) === index; + return domainList.indexOf(domain.toLowerCase()) === index; }); return domains; @@ -279,6 +281,6 @@ export default class CloudflareClient { private getDomainByRecordName(recordName: string): string { const parsedDomain: parseDomain.ParsedDomain = parseDomain(recordName); - return `${parsedDomain.domain}.${parsedDomain.tld}`; + return `${parsedDomain.domain}.${parsedDomain.tld}`.toLowerCase(); } } diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts index 7786c9b..d70ea5d 100644 --- a/src/tests/cloudflare-client-test.ts +++ b/src/tests/cloudflare-client-test.ts @@ -23,7 +23,7 @@ describe('Cloudflare Client', () => { await cloudflareClient.removeRecord(zoneId, record.id); const indexOfRecord: number = recordsToCleanUp.findIndex((recordToCleanup: Record) => { - return record.name = recordToCleanup.name; + return record.name.toLowerCase() === recordToCleanup.name.toLowerCase(); }); recordsToCleanUp.splice(indexOfRecord, 1); @@ -97,13 +97,13 @@ describe('Cloudflare Client', () => { const recordData: Array = await cloudflareClient.getRecordDataForRecords(records); const recordDataNames: Array = recordData.map((recordDataEntry: RecordData) => { - return recordDataEntry.name; + return recordDataEntry.name.toLowerCase(); }); expect(recordData.length).to.equal(records.length); for (const record of records) { - expect(recordDataNames).to.contain(record.name); + expect(recordDataNames).to.contain(record.name.toLowerCase()); } // Cleanup diff --git a/src/tests/test-service/test-service.ts b/src/tests/test-service/test-service.ts index 139b8c5..9b3b4dc 100644 --- a/src/tests/test-service/test-service.ts +++ b/src/tests/test-service/test-service.ts @@ -41,10 +41,10 @@ export default class TestService { private static getRandomSubdomain(): string { let result: string = ''; - const characters: string = 'abcdefghijklmnopqrstuvwxyz0123456789'; + const characters: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-'; const charactersLength: number = characters.length; - for ( let index: number = 0; index < 5; index++ ) { + for (let index: number = 0; index < 5; index++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } From 1310d43e35bc19437a93f8840ec056174e9d7086 Mon Sep 17 00:00:00 2001 From: Steffen Date: Tue, 17 Sep 2019 22:56:32 +0200 Subject: [PATCH 49/70] :recycle: Improve Error Messages --- src/index.ts | 6 +++- src/lib/cloudflare-client.ts | 66 +++++++++++++++++++++++++----------- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/index.ts b/src/index.ts index 512d190..d9436d2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,10 @@ export default class CloudflareDDNSSync { this.cloudflareClient = new CloudflareClient(email, authKey); } + public getRecordDataForRecord(record: Record): Promise { + return this.cloudflareClient.getRecordDataForRecord(record); + } + public getRecordDataForRecords(records: Array): Promise> { return this.cloudflareClient.getRecordDataForRecords(records); } @@ -47,7 +51,7 @@ export default class CloudflareDDNSSync { this.cloudflareClient.removeRecord(zoneId, recordId); } - public async sync(record: Record, ip?: string): SingleSyncResult { + public async syncRecord(record: Record, ip?: string): SingleSyncResult { const ipToUse: string = ip ? ip : await ipUtils.getIp(); diff --git a/src/lib/cloudflare-client.ts b/src/lib/cloudflare-client.ts index ef3fa34..0365995 100644 --- a/src/lib/cloudflare-client.ts +++ b/src/lib/cloudflare-client.ts @@ -4,6 +4,9 @@ import parseDomain from 'parse-domain'; import {DomainRecordList, Record, RecordData, ZoneData, ZoneMap} from '../contracts'; import IPUtils from './ip-utils'; +const ipv4Regex: RegExp = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/; +const ipv6Regex: RegExp = /^(?:(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-fA-F]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,1}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,2}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,3}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:[0-9a-fA-F]{1,4})):)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,4}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,5}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,6}(?:(?:[0-9a-fA-F]{1,4})))?::))))$/; + export default class CloudflareClient { private cloudflare: Cloudflare; @@ -64,11 +67,7 @@ export default class CloudflareClient { } public async removeRecord(zoneId: string, recordId: string): Promise { - try { - await this.cloudflare.dnsRecords.del(zoneId, recordId); - } catch (error) { - throw error; - } + return this.cloudflare.dnsRecords.del(zoneId, recordId); } public async getZoneIdByRecordName(recordName: string): Promise { @@ -157,16 +156,26 @@ export default class CloudflareClient { copyOfRecord.type = copyOfRecord.type ? copyOfRecord.type : 'A'; if (!copyOfRecord.content) { - throw Error(`Could not create Record "${copyOfRecord.name}": Ip is missing!`); + throw Error(`Could not create Record "${copyOfRecord.name}": Content is missing!`); } - try { - const response: Response & {result: RecordData} = await this.cloudflare.dnsRecords.add(zoneId, copyOfRecord); - - return response.result; - } catch (error) { - throw error; + if (copyOfRecord.type === 'A') { + if (!copyOfRecord.content.match(ipv4Regex)) { + throw Error(`Could not create Record "${copyOfRecord.name}": '${copyOfRecord.content}' is not a valid ipv4!`); + } + } else if (copyOfRecord.type === 'AAAA') { + if (!copyOfRecord.content.match(ipv6Regex)) { + throw Error(`Could not create Record "${copyOfRecord.name}": '${copyOfRecord.content}' is not a valid ipv6!`); + } + } else if (copyOfRecord.type === 'CNAME') { + if (parseDomain(copyOfRecord.content) === null) { + throw Error(`Could not create Record "${copyOfRecord.name}": '${copyOfRecord.content}' is not a valid domain name!`); + } } + + const response: Response & {result: RecordData} = await this.cloudflare.dnsRecords.add(zoneId, copyOfRecord); + + return response.result; } private async updateRecord(zoneId: string, recordId: string, record: Record, ip?: string): Promise { @@ -176,16 +185,26 @@ export default class CloudflareClient { copyOfRecord.type = copyOfRecord.type ? copyOfRecord.type : 'A'; if (!copyOfRecord.content) { - throw Error(`Could not update Record "${record.name}": Ip is missing!`); + throw Error(`Could not create Record "${copyOfRecord.name}": Content is missing!`); } - try { - const response: Response & {result: RecordData} = await this.cloudflare.dnsRecords.edit(zoneId, recordId, copyOfRecord); - - return response.result; - } catch (error) { - throw error; + if (copyOfRecord.type === 'A') { + if (!copyOfRecord.content.match(ipv4Regex)) { + throw Error(`Could not create Record "${copyOfRecord.name}": '${copyOfRecord.content}' is not a valid ipv4!`); + } + } else if (copyOfRecord.type === 'AAAA') { + if (!copyOfRecord.content.match(ipv6Regex)) { + throw Error(`Could not create Record "${copyOfRecord.name}": '${copyOfRecord.content}' is not a valid ipv6!`); + } + } else if (copyOfRecord.type === 'CNAME') { + if (parseDomain(copyOfRecord.content) === null) { + throw Error(`Could not create Record "${copyOfRecord.name}": '${copyOfRecord.content}' is not a valid domain name!`); + } } + + const response: Response & {result: RecordData} = await this.cloudflare.dnsRecords.edit(zoneId, recordId, copyOfRecord); + + return response.result; } private async updateZoneMap(): Promise { @@ -262,6 +281,11 @@ export default class CloudflareClient { return zoneId; } else { await this.updateZoneMap(); + + if (!this.zoneMap.has(domain)) { + throw new Error(`Could not find domain '${domain}'. Make sure the domain is set up for your cloudflare account.`); + } + const zoneId: string = this.zoneMap.get(domain.toLowerCase()); return zoneId; @@ -281,6 +305,10 @@ export default class CloudflareClient { private getDomainByRecordName(recordName: string): string { const parsedDomain: parseDomain.ParsedDomain = parseDomain(recordName); + if (parsedDomain === null) { + throw new Error(`Could not parse domain. '${JSON.stringify(recordName)}' is not a valid record name.`); + } + return `${parsedDomain.domain}.${parsedDomain.tld}`.toLowerCase(); } } From 39e6319a4246215b30a84988d2470ec8e48b8c62 Mon Sep 17 00:00:00 2001 From: Steffen Date: Tue, 17 Sep 2019 22:56:48 +0200 Subject: [PATCH 50/70] :lipstick: Improve Random Subdomain Names --- src/tests/test-service/test-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/test-service/test-service.ts b/src/tests/test-service/test-service.ts index 9b3b4dc..8c87385 100644 --- a/src/tests/test-service/test-service.ts +++ b/src/tests/test-service/test-service.ts @@ -41,7 +41,7 @@ export default class TestService { private static getRandomSubdomain(): string { let result: string = ''; - const characters: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-'; + const characters: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength: number = characters.length; for (let index: number = 0; index < 5; index++) { From 24e9eaffd30e9fb3df4025343b4c2d832d95b23a Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 00:27:42 +0200 Subject: [PATCH 51/70] :lipstick: Sort Functions Alphabetically --- src/index.ts | 54 ++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/index.ts b/src/index.ts index d9436d2..96ee647 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,22 +20,14 @@ export default class CloudflareDDNSSync { this.cloudflareClient = new CloudflareClient(email, authKey); } - public getRecordDataForRecord(record: Record): Promise { - return this.cloudflareClient.getRecordDataForRecord(record); - } - - public getRecordDataForRecords(records: Array): Promise> { - return this.cloudflareClient.getRecordDataForRecords(records); + public async createRecord(record: Record): Promise { + return this.cloudflareClient.syncRecord(record); } public getIp(): Promise { return ipUtils.getIp(); } - public async createRecord(record: Record): Promise { - return this.cloudflareClient.syncRecord(record); - } - public async getRecordDataForDomain(domain: string): Promise> { return this.cloudflareClient.getRecordDataForDomain(domain); } @@ -44,6 +36,14 @@ export default class CloudflareDDNSSync { return this.cloudflareClient.getRecordDataForDomains(domains); } + public getRecordDataForRecord(record: Record): Promise { + return this.cloudflareClient.getRecordDataForRecord(record); + } + + public getRecordDataForRecords(records: Array): Promise> { + return this.cloudflareClient.getRecordDataForRecords(records); + } + public async removeRecord(recordName: string): Promise { const zoneId: string = await this.cloudflareClient.getZoneIdByRecordName(recordName); const recordId: string = await this.cloudflareClient.getRecordIdByName(recordName); @@ -51,19 +51,16 @@ export default class CloudflareDDNSSync { this.cloudflareClient.removeRecord(zoneId, recordId); } - public async syncRecord(record: Record, ip?: string): SingleSyncResult { - - const ipToUse: string = ip ? ip : await ipUtils.getIp(); - - return this.cloudflareClient.syncRecord(record, ipToUse); + public stopSyncOnIpChange(changeListenerId: string): void { + ipUtils.removeIpChangeListener(changeListenerId); } - public async syncRecords(records: Array, ip?: string): MultiSyncResult { - const currentIp: string = await ipUtils.getIp(); - - const ipToUse: string = ip ? ip : currentIp; + public syncByCronTime(cronExpression: string, records: Array, callback: MultiSyncCallback, ip?: string): ScheduledTask { + return Cron.createCronJob(cronExpression, async() => { + const result: Array = await this.syncRecords(records, ip); - return this.cloudflareClient.syncRecords(records, ipToUse); + callback(result); + }); } public async syncOnIpChange(records: Array, callback: MultiSyncCallback): Promise { @@ -81,16 +78,19 @@ export default class CloudflareDDNSSync { return changeListenerId; } - public stopSyncOnIpChange(changeListenerId: string): void { - ipUtils.removeIpChangeListener(changeListenerId); + public async syncRecord(record: Record, ip?: string): SingleSyncResult { + + const ipToUse: string = ip ? ip : await ipUtils.getIp(); + + return this.cloudflareClient.syncRecord(record, ipToUse); } - public syncByCronTime(cronExpression: string, records: Array, callback: MultiSyncCallback, ip?: string): ScheduledTask { - return Cron.createCronJob(cronExpression, async() => { - const result: Array = await this.syncRecords(records, ip); + public async syncRecords(records: Array, ip?: string): MultiSyncResult { + const currentIp: string = await ipUtils.getIp(); - callback(result); - }); + const ipToUse: string = ip ? ip : currentIp; + + return this.cloudflareClient.syncRecords(records, ipToUse); } } From d4d29729befeb58ef78f0e6a01a8f0e98711ed2d Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 00:29:53 +0200 Subject: [PATCH 52/70] :fire: Remove Unneeded Code --- src/lib/cloudflare-client.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/lib/cloudflare-client.ts b/src/lib/cloudflare-client.ts index 0365995..f2c50d4 100644 --- a/src/lib/cloudflare-client.ts +++ b/src/lib/cloudflare-client.ts @@ -82,9 +82,7 @@ export default class CloudflareClient { return record.id; } - // TODO: Performance? public async getRecordDataForRecord(record: Record): Promise { - // console.time('getRecordDataForRecord'); const domain: string = this.getDomainByRecordName(record.name); const recordDataForDomain: Array = await this.getRecordsByDomain(domain); @@ -93,13 +91,10 @@ export default class CloudflareClient { return record.name.toLowerCase() === singleRecordData.name.toLowerCase(); }); - // console.timeEnd('getRecordDataForRecord'); return recordData; } - // TODO: Performance? public async getRecordDataForRecords(records: Array): Promise> { - // console.time('getRecordDataForRecords'); const domains: Array = this.getDomainsFromRecords(records); const recordDataPromises: Array>> = domains.map(async(domain: string) => { @@ -117,14 +112,10 @@ export default class CloudflareClient { const recordDataForDomains: Array> = await Promise.all(recordDataPromises); const recordData: Array = [].concat(...recordDataForDomains); - // console.timeEnd('getRecordDataForRecords'); return recordData; } - // TODO: Performance? public async getRecordDataForDomains(domains: Array): Promise { - // console.time('getRecordDataForDomains'); - const recordDataPromises: Array>> = domains.map(async(domain: string) => { return this.getRecordDataForDomain(domain); }); @@ -136,15 +127,11 @@ export default class CloudflareClient { recordData[domains[index]] = recordDataForDomain; }); - // console.timeEnd('getRecordDataForDomains'); return recordData; } - // TODO: Performance? public async getRecordDataForDomain(domain: string): Promise> { - // console.time('getRecordDataForDomain'); const recordData: Array = await this.getRecordsByDomain(domain); - // console.timeEnd('getRecordDataForDomain'); return recordData; } @@ -234,10 +221,7 @@ export default class CloudflareClient { return record; } - // TODO: Performance? private async getRecordIdsForRecords(records: Array): Promise> { - // console.time('getRecordIdsToUpdate'); - const recordIdMap: Map = new Map(); const recordData: Array = await this.getRecordDataForRecords(records); @@ -246,8 +230,6 @@ export default class CloudflareClient { recordIdMap.set(record.name.toLowerCase(), record.id); } - // console.timeEnd('getRecordIdsToUpdate'); - return recordIdMap; } From 2f067f00dad1891bdb1347349be9992ccbfefca3 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 19:38:15 +0200 Subject: [PATCH 53/70] :lipstick: Add Missing Return --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 96ee647..5a25763 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,7 +48,7 @@ export default class CloudflareDDNSSync { const zoneId: string = await this.cloudflareClient.getZoneIdByRecordName(recordName); const recordId: string = await this.cloudflareClient.getRecordIdByName(recordName); - this.cloudflareClient.removeRecord(zoneId, recordId); + return this.cloudflareClient.removeRecord(zoneId, recordId); } public stopSyncOnIpChange(changeListenerId: string): void { From e0178deef76649f5dba694d198a7a56ed3191048 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 21:15:02 +0200 Subject: [PATCH 54/70] :bug: Fix Exports --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 5a25763..e8c37bd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -94,4 +94,4 @@ export default class CloudflareDDNSSync { } } -module.exports = CloudflareDDNSSync; +export * from './contracts/index'; From 2cf6854f7934dd37f89dcc50fa86f1e587617d09 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 21:15:26 +0200 Subject: [PATCH 55/70] :lipstick: Adjust Package Description --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b7feee9..199105b 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cloudflare-ddns-sync", "version": "1.5.4", - "description": "A simple package to update DNS records on Cloudflare whenever you want", + "description": "A simple module to update DNS records on Cloudflare whenever you want", "main": "index.js", "author": "Steffen Knaup ", "license": "ISC", From 7ad212126ee6cc581b08bc04d607e974adbe143a Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 21:15:46 +0200 Subject: [PATCH 56/70] :recycle: Adjust Main File --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 199105b..ce14341 100755 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "cloudflare-ddns-sync", "version": "1.5.4", "description": "A simple module to update DNS records on Cloudflare whenever you want", - "main": "index.js", + "main": "dist/index.js", "author": "Steffen Knaup ", "license": "ISC", "keywords": [ From 14bffa8b360ebab60f07265712e0bf82fe036a2d Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 21:21:29 +0200 Subject: [PATCH 57/70] :fire: Remove Unneeded Function --- src/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index e8c37bd..2069060 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,10 +20,6 @@ export default class CloudflareDDNSSync { this.cloudflareClient = new CloudflareClient(email, authKey); } - public async createRecord(record: Record): Promise { - return this.cloudflareClient.syncRecord(record); - } - public getIp(): Promise { return ipUtils.getIp(); } From a5107b8cf69f036a2c18328e2158766eb262592d Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 21:27:25 +0200 Subject: [PATCH 58/70] :memo: Update Readme --- README.md | 112 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 66 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index fc92d71..c7c6f29 100644 --- a/README.md +++ b/README.md @@ -3,22 +3,15 @@ ![](/~https://github.com/SteffenKn/Cloudflare-ddns-sync/workflows/Test-Workflow/badge.svg) [![NPM](https://nodei.co/npm/cloudflare-ddns-sync.png)](https://www.npmjs.com/package/cloudflare-ddns-sync) - -[Documentation](https://cds.knaup.pw/) - -You may also have a look at the **official** CLI version of Cloudflare-DDNS-Sync: - [![NPM](https://nodei.co/npm/cloudflare-ddns-sync-cli.png)](https://www.npmjs.com/package/cloudflare-ddns-sync-cli) + ## Overview -Cloudflare-DDNS-Sync is a simple NPM package that updates the IP address of -Cloudflare DNS records. +Cloudflare-DDNS-Sync is a simple module that updates Cloudflare DNS records. -## What are the goals of this project? -The goal of Cloudflare-DDNS-Sync is to make updating the IP of Cloudflare DNS -records as easy as possible. +You may also have a look at the **official** [CLI version](https://www.npmjs.com/package/cloudflare-ddns-sync-cli) of Cloudflare-DDNS-Sync. ## How do I set this project up? @@ -39,57 +32,84 @@ in your project folder. ## Usage -```javascript -var CloudflareDDNSSync = require("cloudflare-ddns-sync"); +> Hint: If a record is not existing, CDS will automatically create it when +syncing. -var ddnsSync = new CloudflareDDNSSync({ - "auth" : { - "email" : "your@email.com", - "key" : "your_cloudflare_api_key" - }, - "domain": "your-domain.com", - "records" : [ - "subdomain.your-domain.com", - "subdomain2.your-domain.com" - ], -}); +### Javascript Example -ddnsSync.sync() -.then((results) => { - for(var result of results){ - console.log(result); +```javascript +const Cddnss = require('cloudflare-ddns-sync').default; + +const cddnss = new Cddnss('your@email.com', ''); + +const records = [ + { + name: 'test-1.domain.com', + type: 'A', // optional + proxied: true, // optional + ttl: 1, // optional + priority: 0, // optional + content: '1.2.3.4', // optional + }, + { + name: "test-2.domain.com" } +]; + +cddnss.syncRecords(records).then((result) => { + console.log(result); }); ``` -> Hint: If a record is not existing, CDS will automatically create it when -syncing. +### Typescript Example + +```typescript +import Cddnss, {Record, RecordData} from 'cloudflare-ddns-sync'; + +const cddnss: Cddnss = new Cddnss('your@email.com', ''); + +const records: Array = [ + { + name: 'test-1.cddnss.pw', + type: 'A', // optional + proxied: true, // optional + ttl: 1, // optional + priority: 0, // optional + content: '1.2.3.4', // optional + }, + { + name: "test-2.cddnss.pw" + }, +]; + +cddnss.syncRecords(records).then((result: Array) => { + console.log(result); +}) +``` ## Methods -- getIp() -- getRecordIps() -- sync(\) -- syncOnIpChange(\) -- syncByInterval(interval, \, \) -- syncOnceEveryHour(minute, \, \) -- syncOnceEveryDay([hour, \], \, \) -- syncOnceEveryWeek([dayOfWeek, \, \], \, \) -- syncOnceEveryMonth([dayOfMonth, \, \], \, \) -- syncByCronTime(cronTime, \, \) -- syncAtDate(date, \, \) -- syncByTimestring(timestring, \, \) -- stopSyncOnIpChange() - -For a more detailed view, have a look at the [Documentation](https://cds.knaup.pw/methods.html) +- getIp(): Promise\ +- getRecordDataForDomain(domain: string): Promise\\> +- getRecordDataForDomains(domains: Array\): Promise\<[DomainRecordList](https://docu.cddnss.pw/types/RecordDataList)\> +- getRecordDataForRecord(record: [Record](https://docu.cddnss.pw/types/Record)): Promise\<[RecordData](https://docu.cddnss.pw/types/RecordData)\> +- getRecordDataForRecords(records: Array\<[Record](https://docu.cddnss.pw/types/Record)\>): Promise\\> +- removeRecord(recordName: string): Promise\ +- stopSyncOnIpChange(changeListenerId: string): void +- syncByCronTime(cronExpression: string, records: Array\<[Record](https://docu.cddnss.pw/types/RecordData)\>, callback: [MultiSyncCallback](https://docu.cddnss.pw/types/MultiSyncCallback), ip?: string): [ScheduledTask](https://www.npmjs.com/package/node-cron#scheduledtask-methods) +- syncOnIpChange(records: Array\<[Record](https://docu.cddnss.pw/types/Record)\>, callback: MultiSyncCallback): Promise\ +- syncRecord(record: [Record](https://docu.cddnss.pw/types/Record), ip?: string): [SingleSyncResult](https://docu.cddnss.pw/types/SingleSyncResult) +- syncRecords(records: Array\<[Record](https://docu.cddnss.pw/types/Record)\>, ip?: string): [MultiSyncResult](https://docu.cddnss.pw/types/MultiSyncResult) + +For a more detailed view, have a look at the [Documentation](https://docu.cddnss.pw/) ## Get Your Cloudflare API Key - Go to **[Cloudflare](https://www.cloudflare.com)** - **Log In** -- In the upper right corner: **click on your email address** +- In the upper right corner: **click on the user icon** - Go to **"My Profile"** -- In the "API Key"-Section: **click on the "View API Key"-Button of the Global Key** +- In the "API Tokens"-Section: **click on the "View"-Button of the Global Key** - **Enter your password** and **fill the captcha** - **Copy the API Key** From eb89c1a95b09342b00cb5fc9f7499d01e3218d0c Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 23:25:25 +0200 Subject: [PATCH 59/70] :recycle: Refactor Return Type of Sync Functions --- src/contracts/SyncResult.ts | 4 ---- src/contracts/index.ts | 1 - src/index.ts | 6 ++---- 3 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 src/contracts/SyncResult.ts diff --git a/src/contracts/SyncResult.ts b/src/contracts/SyncResult.ts deleted file mode 100644 index 27a38ee..0000000 --- a/src/contracts/SyncResult.ts +++ /dev/null @@ -1,4 +0,0 @@ -import {RecordData} from './index'; - -export type SingleSyncResult = Promise; -export type MultiSyncResult = Promise>; diff --git a/src/contracts/index.ts b/src/contracts/index.ts index 2e9a1d9..83cfbbc 100644 --- a/src/contracts/index.ts +++ b/src/contracts/index.ts @@ -3,5 +3,4 @@ export * from './cloudflare/index'; export * from './Callbacks'; export * from './DomainRecordList'; export * from './Record'; -export * from './SyncResult'; export * from './ZoneMap'; diff --git a/src/index.ts b/src/index.ts index 2069060..2304f8e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,10 +7,8 @@ import ipUtils from './lib/ip-utils'; import { DomainRecordList, MultiSyncCallback, - MultiSyncResult, Record, RecordData, - SingleSyncResult, } from './contracts/index'; export default class CloudflareDDNSSync { @@ -74,14 +72,14 @@ export default class CloudflareDDNSSync { return changeListenerId; } - public async syncRecord(record: Record, ip?: string): SingleSyncResult { + public async syncRecord(record: Record, ip?: string): Promise { const ipToUse: string = ip ? ip : await ipUtils.getIp(); return this.cloudflareClient.syncRecord(record, ipToUse); } - public async syncRecords(records: Array, ip?: string): MultiSyncResult { + public async syncRecords(records: Array, ip?: string): Promise> { const currentIp: string = await ipUtils.getIp(); const ipToUse: string = ip ? ip : currentIp; From c7513fcdaeff31faa152854e5480b756b3de3085 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 23:25:59 +0200 Subject: [PATCH 60/70] :recycle: Adjust Links in Readme --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c7c6f29..27a7afa 100644 --- a/README.md +++ b/README.md @@ -90,16 +90,16 @@ cddnss.syncRecords(records).then((result: Array) => { ## Methods - getIp(): Promise\ -- getRecordDataForDomain(domain: string): Promise\\> -- getRecordDataForDomains(domains: Array\): Promise\<[DomainRecordList](https://docu.cddnss.pw/types/RecordDataList)\> -- getRecordDataForRecord(record: [Record](https://docu.cddnss.pw/types/Record)): Promise\<[RecordData](https://docu.cddnss.pw/types/RecordData)\> -- getRecordDataForRecords(records: Array\<[Record](https://docu.cddnss.pw/types/Record)\>): Promise\\> +- getRecordDataForDomain(domain: string): Promise\\> +- getRecordDataForDomains(domains: Array\): Promise\<[DomainRecordList](https://docu.cddnss.pw/types/domainrecordlist)\> +- getRecordDataForRecord(record: [Record](https://docu.cddnss.pw/types/record)): Promise\<[RecordData](https://docu.cddnss.pw/types/recorddata)\> +- getRecordDataForRecords(records: Array\<[Record](https://docu.cddnss.pw/types/record)\>): Promise\\> - removeRecord(recordName: string): Promise\ - stopSyncOnIpChange(changeListenerId: string): void -- syncByCronTime(cronExpression: string, records: Array\<[Record](https://docu.cddnss.pw/types/RecordData)\>, callback: [MultiSyncCallback](https://docu.cddnss.pw/types/MultiSyncCallback), ip?: string): [ScheduledTask](https://www.npmjs.com/package/node-cron#scheduledtask-methods) -- syncOnIpChange(records: Array\<[Record](https://docu.cddnss.pw/types/Record)\>, callback: MultiSyncCallback): Promise\ -- syncRecord(record: [Record](https://docu.cddnss.pw/types/Record), ip?: string): [SingleSyncResult](https://docu.cddnss.pw/types/SingleSyncResult) -- syncRecords(records: Array\<[Record](https://docu.cddnss.pw/types/Record)\>, ip?: string): [MultiSyncResult](https://docu.cddnss.pw/types/MultiSyncResult) +- syncByCronTime(cronExpression: string, records: Array\<[Record](https://docu.cddnss.pw/types/recorddata)\>, callback: [MultiSyncCallback](https://docu.cddnss.pw/types/multisynccallback), ip?: string): [ScheduledTask](https://www.npmjs.com/package/node-cron#scheduledtask-methods) +- syncOnIpChange(records: Array\<[Record](https://docu.cddnss.pw/types/record)\>, callback: multisynccallback): Promise\ +- syncRecord(record: [Record](https://docu.cddnss.pw/types/record), ip?: string): Promise\<[RecordData](https://docu.cddnss.pw/types/recorddata)\> +- syncRecords(records: Array\<[Record](https://docu.cddnss.pw/types/record)\>, ip?: string): Promise\\> For a more detailed view, have a look at the [Documentation](https://docu.cddnss.pw/) From 6f0870170f50bb52a570901c0790e8bf67d3e1d6 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 23:26:14 +0200 Subject: [PATCH 61/70] :sparkles: Add Cron Expression Syntax to Readme --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 27a7afa..7e2d487 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,22 @@ cddnss.syncRecords(records).then((result: Array) => { }) ``` +### Cron Expression Syntax + +Cron expressions have the following syntax: + +``` +* * * * * * +┬ ┬ ┬ ┬ ┬ ┬ +│ │ │ │ │ │ +│ │ │ │ │ └──── weekday (0-7, sunday is 0 or 7) +│ │ │ │ └────── month (1-12) +│ │ │ └──────── day (1-31) +│ │ └────────── hour (0-23) +│ └──────────── minute (0-59) +└────────────── second (0-59) [optional] +``` + ## Methods - getIp(): Promise\ From ebf00c5d609b00048220c93b95dd21966cf49d48 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 23:43:23 +0200 Subject: [PATCH 62/70] :recycle: Move CloudflareClient Related Code to CloudflareClient --- src/index.ts | 5 +---- src/lib/cloudflare-client.ts | 5 ++++- src/tests/cloudflare-client-test.ts | 8 ++------ 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2304f8e..f51ad74 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,10 +39,7 @@ export default class CloudflareDDNSSync { } public async removeRecord(recordName: string): Promise { - const zoneId: string = await this.cloudflareClient.getZoneIdByRecordName(recordName); - const recordId: string = await this.cloudflareClient.getRecordIdByName(recordName); - - return this.cloudflareClient.removeRecord(zoneId, recordId); + return this.cloudflareClient.removeRecordByName(recordName); } public stopSyncOnIpChange(changeListenerId: string): void { diff --git a/src/lib/cloudflare-client.ts b/src/lib/cloudflare-client.ts index f2c50d4..edacd69 100644 --- a/src/lib/cloudflare-client.ts +++ b/src/lib/cloudflare-client.ts @@ -66,7 +66,10 @@ export default class CloudflareClient { return results; } - public async removeRecord(zoneId: string, recordId: string): Promise { + public async removeRecordByName(recordName: string): Promise { + const zoneId: string = await this.getZoneIdByRecordName(recordName); + const recordId: string = await this.getRecordIdByName(recordName); + return this.cloudflare.dnsRecords.del(zoneId, recordId); } diff --git a/src/tests/cloudflare-client-test.ts b/src/tests/cloudflare-client-test.ts index d70ea5d..e0c7d1c 100644 --- a/src/tests/cloudflare-client-test.ts +++ b/src/tests/cloudflare-client-test.ts @@ -18,9 +18,7 @@ describe('Cloudflare Client', () => { const recordData: Array = await cloudflareClient.getRecordDataForRecords(recordsToCleanUp); for (const record of recordData) { - const zoneId: string = record.zone_id; - - await cloudflareClient.removeRecord(zoneId, record.id); + await cloudflareClient.removeRecordByName(record.name); const indexOfRecord: number = recordsToCleanUp.findIndex((recordToCleanup: Record) => { return record.name.toLowerCase() === recordToCleanup.name.toLowerCase(); @@ -81,11 +79,9 @@ describe('Cloudflare Client', () => { const record: Record = TestService.getTestData().records[0]; record.content = '1.2.3.4'; await cloudflareClient.syncRecord(record); - const zoneId: string = await cloudflareClient.getZoneIdByRecordName(record.name); - const recordId: string = await cloudflareClient.getRecordIdByName(record.name); // Prepare END - await cloudflareClient.removeRecord(zoneId, recordId); + await cloudflareClient.removeRecordByName(record.name); }); it('should get record data for records', async() => { From 9e7e5e3aa99b5965d7660c7ae6f075b6a8494965 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 23:43:40 +0200 Subject: [PATCH 63/70] :lipstick: Add Comment --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index f51ad74..04e23d2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -61,6 +61,7 @@ export default class CloudflareDDNSSync { callback(result); }); + // Sync records to make sure the current ip is already set. const currentIp: string = await ipUtils.getIp(); this.syncRecords(records, currentIp).then((syncedRecords: Array): void => { callback(syncedRecords); From a5e93d52f7ae13b9002b531764b2dfa28be4814d Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 18 Sep 2019 23:59:48 +0200 Subject: [PATCH 64/70] :memo: Add Test Section to Readme --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 7e2d487..86bfb5d 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,20 @@ For a more detailed view, have a look at the [Documentation](https://docu.cddnss - **Enter your password** and **fill the captcha** - **Copy the API Key** +## Tests + +In order to run the tests there are two ways to do so + +### Use `test-data.json` + +- Open the `test-data.json` which can be found under `src/tests/test-service/` +- Configure the email, cloudflare api key and the domain +- Run `npm test` + +### Use `npm test` Only + +- Run `npm test -- --email="your@email.com" --key="your_cloudflare_api_key" --domain="yourdomain.com"` + ## Changelog ### v1.5.4 From 5c9eb06db84ae4227b4fe455854720f9b24c3269 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 19 Sep 2019 00:00:08 +0200 Subject: [PATCH 65/70] :sparkles: Add Error Handling for Missing Test Data --- src/tests/test-service/test-service.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/tests/test-service/test-service.ts b/src/tests/test-service/test-service.ts index 8c87385..4d0e414 100644 --- a/src/tests/test-service/test-service.ts +++ b/src/tests/test-service/test-service.ts @@ -22,6 +22,20 @@ export default class TestService { records: this.getRandomRecords(5, domain), }; + const testDataNotProvided: boolean = !testData.auth.email + || testData.auth.email === 'your@email.com' + || !testData.auth.key + || testData.auth.key === 'your_cloudflare_api_key' + || !testData.domain + || testData.domain === 'yourdomain.com'; + + if (testDataNotProvided) { + // tslint:disable-next-line:no-console + console.log(`In order to use the tests you must provide some data via 'src/tests/test-service/test-data.json' or via 'npm test -- --email="your@email.com" --key="cloudflare-key" --domain="domain.com"'`); + + process.exit(); + } + return testData; } From 4a1f457479100aa4731c7d672213a159294ff2c2 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 19 Sep 2019 00:04:43 +0200 Subject: [PATCH 66/70] :memo: Adjust Examples --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 86bfb5d..88cab4b 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ const cddnss: Cddnss = new Cddnss('your@email.com', '') const records: Array = [ { - name: 'test-1.cddnss.pw', + name: 'test-1.yourdomain.com', type: 'A', // optional proxied: true, // optional ttl: 1, // optional @@ -78,13 +78,13 @@ const records: Array = [ content: '1.2.3.4', // optional }, { - name: "test-2.cddnss.pw" + name: "test-2.yourdomain.com" }, ]; cddnss.syncRecords(records).then((result: Array) => { console.log(result); -}) +}); ``` ### Cron Expression Syntax From 517c9e7a8878f5a9651c3b48bd871299065b36af Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 19 Sep 2019 00:09:52 +0200 Subject: [PATCH 67/70] :memo: Add 'v2.0.0' to Changelog --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 88cab4b..be710a7 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,10 @@ In order to run the tests there are two ways to do so ## Changelog +### v2.0.0 + +- ♻️ **Rewrite Cloudflare-DDNS-Sync in Typescript** + ### v1.5.4 - 📝 Update README From cad048b998fa980bffbdfadde69f5ae9afae6c0d Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 19 Sep 2019 00:10:43 +0200 Subject: [PATCH 68/70] :lipstick: Adjust NPM Keywords --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ce14341..779ce19 100755 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "license": "ISC", "keywords": [ "cloudflare", + "cddnss", "ddns", "dns", "dyndns", From 2cff3df70d7f303ec5baa29ed2f7b2f251991539 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 19 Sep 2019 00:12:43 +0200 Subject: [PATCH 69/70] :arrow_up: Update Dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 779ce19..a8c0a8a 100755 --- a/package.json +++ b/package.json @@ -24,14 +24,14 @@ "test-jenkins": "JUNIT_REPORT_PATH=report.xml npm test -- --reporter mocha-jenkins-reporter" }, "dependencies": { - "cloudflare": "2.6.0", + "cloudflare": "2.7.0", "node-cron": "2.0.3", "parse-domain": "2.3.2", "public-ip": "3.2.0", "what-is-my-ip-address": "1.0.3" }, "devDependencies": { - "@types/chai": "4.2.2", + "@types/chai": "4.2.3", "@types/minimist": "1.2.0", "@types/mocha": "5.2.7", "@types/node": "10.14.17", From 9dbc8ab35301f3a820a97cb09536b566b4ca0f6e Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 19 Sep 2019 00:11:02 +0200 Subject: [PATCH 70/70] :bookmark: Set Version to '2.0.0' --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a8c0a8a..1ba284c 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudflare-ddns-sync", - "version": "1.5.4", + "version": "2.0.0", "description": "A simple module to update DNS records on Cloudflare whenever you want", "main": "dist/index.js", "author": "Steffen Knaup ",