safelinkify
Version:
NodeJS anonymizer external links into outbound page. Anonymize external links to outbound page redirector for SEO.
253 lines (252 loc) • 12.8 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var encryptionURL_1 = __importDefault(require("./encryptionURL"));
var parseQuery_1 = __importDefault(require("./parseQuery"));
var resolveQueryUrl_1 = __importDefault(require("./resolveQueryUrl"));
var string_1 = require("./string");
var toURL_1 = __importDefault(require("./toURL"));
var _global_safelink = (typeof window !== 'undefined' ? window : global);
var safelink = /** @class */ (function () {
function safelink(opt) {
this.options = {
exclude: [],
redirect: [], // 'https://www.webmanajemen.com/page/safelink.html?url='
password: 'root',
verbose: false,
type: 'base64'
};
this.parseQuery = parseQuery_1.default;
if (opt && typeof opt.redirect == 'string')
opt.redirect = [opt.redirect];
this.options = Object.assign(this.options, opt);
}
/**
* is url excluded
* @param url
* @returns
*/
safelink.prototype.isExcluded = function (url) {
var excludes = this.options.exclude;
var value = String(url);
var parsed = url instanceof URL ? url : (0, toURL_1.default)(value);
// skip empty string
if (typeof url === 'string' && url.length === 0) {
return true;
}
// skip url starts with ? and #
if (value.match(/^(\?|#)/)) {
return true;
}
// only process url with protocol
if (value.match(/^(?:(ht|f)tp(s?):\/\/)?/)) {
for (var i = 0; i < excludes.length; i++) {
var pattern = excludes[i];
if (typeof pattern == 'string' && typeof parsed === 'object' && parsed !== null) {
// only validate full url
if ((parsed.host || '').includes(pattern))
return true;
}
else if (pattern instanceof RegExp) {
if (value.match(pattern))
return true;
}
}
}
return false;
};
/**
* parse html string or element to anonymize urls
* @param target
* @returns
*/
safelink.prototype.parse = function (target) {
return __awaiter(this, void 0, void 0, function () {
var self, content, result, regex, processStr, matches, i, m, href, allMatch, wholeContents, processedHyperlink, processedContent, tagname, links, i, a, href, encryption, excluded, enc, randRedir;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
self = this;
content = target;
if (!(typeof target === 'string' || target instanceof HTMLElement)) return [3 /*break*/, 1];
content = target;
return [3 /*break*/, 4];
case 1:
if (!Buffer.isBuffer(target)) return [3 /*break*/, 2];
content = (0, string_1.bufferToString)(target);
return [3 /*break*/, 4];
case 2:
if (!target) return [3 /*break*/, 4];
return [4 /*yield*/, (0, string_1.streamToString)(target)];
case 3:
content = _a.sent();
_a.label = 4;
case 4:
result = null;
if (typeof content === 'string' && content.trim().length > 0) {
// make content as default result
result = content;
regex = /<a\s+(?:[^>]*?\s+)?href=(["'])(.*?)\1/gim;
processStr = function (content, href) {
var parseUrl = self.parseUrl(href);
if (parseUrl) {
// return anonymized href
return content.replace(href, parseUrl);
}
// return original content
return content;
};
matches = Array.from(content.matchAll(regex)).filter(function (m) { return m[2].trim().match(/^https?:\/\//); });
for (i = 0; i < matches.length; i++) {
m = matches[i];
href = m[2].trim();
allMatch = m[0];
if (typeof href == 'string' && href.length > 0) {
wholeContents = typeof result == 'string' ? result : content;
if (typeof wholeContents === 'string') {
processedHyperlink = processStr(allMatch, href);
if (processedHyperlink) {
processedContent = wholeContents.replace(allMatch, processedHyperlink);
result = processedContent;
}
}
}
}
}
else if (content instanceof HTMLElement) {
tagname = content.tagName.toLowerCase();
if (tagname != 'a') {
links = Array.from(content.querySelectorAll('a'));
for (i = 0; i < links.length; i++) {
a = links[i];
if (!a.href)
continue;
href = (0, toURL_1.default)(a.href);
if (!href) {
console.log(a.href, null);
continue;
}
encryption = (0, encryptionURL_1.default)(href, self.options.password, self.options.verbose);
excluded = self.isExcluded(href);
if (self.options.verbose) {
console.log(Object.assign(encryption, {
url: href.href,
isExcluded: excluded
}));
}
if (!excluded) {
enc = self.options.type == 'base64' ? encryption.base64.encode : encryption.aes.encode;
randRedir = self.options.redirect[Math.floor(Math.random() * self.options.redirect.length)];
a.href = randRedir + enc;
a.target = '_blank';
a.rel = 'nofollow noopener noreferer';
}
}
result = content.outerHTML;
}
}
return [2 /*return*/, result];
}
});
});
};
/**
* parse single url
* @param url
* @returns return redirect url or original url
* * when redirect not set, will return encoded URL only
*/
safelink.prototype.parseUrl = function (url) {
var excluded = this.isExcluded(url);
if (!excluded) {
var encryption = (0, encryptionURL_1.default)(url, this.options.password, this.options.verbose);
var enc = this.options.type == 'base64' ? encryption.base64.encode : encryption.aes.encode;
var randRedir = this.options.redirect[Math.floor(Math.random() * this.options.redirect.length)];
// return anonymized href
if (randRedir)
return randRedir + enc;
return enc;
}
return null;
};
/**
* anonymize url directly
* @param href
*/
safelink.prototype.encodeURL = function (href) {
var self = this;
var encryption = (0, encryptionURL_1.default)(href, self.options.password, self.options.verbose);
var enc = self.options.type == 'base64' ? encryption.base64.encode : encryption.aes.encode;
var randRedir = self.options.redirect[Math.floor(Math.random() * self.options.redirect.length)];
var newhref = randRedir + enc;
return newhref;
};
/**
* Resolve query url to decrypt anonymized urls (page redirector)
* @param search
* @returns
*/
safelink.prototype.resolveQueryUrl = function (search) {
var self = this;
var obj = (0, resolveQueryUrl_1.default)(typeof search == 'string'
? search
: typeof location == 'object' && typeof location.search == 'string'
? location.search
: undefined, this.options.password, this.options.verbose);
if (obj !== null && typeof obj === 'object') {
Object.keys(obj).forEach(function (key) {
var encryptions = obj[key];
if (encryptions) {
if (encryptions.aes.encode) {
encryptions.aes.encode_redirector = self.options.redirect + encryptions.aes.encode;
}
if (encryptions.base64.encode) {
encryptions.base64.encode_redirector = self.options.redirect + encryptions.base64.encode;
}
}
});
}
return obj;
};
return safelink;
}());
exports.default = safelink;
_global_safelink.safelink = safelink;