@fnlb-project/stanza
Version:
Modern XMPP in the browser, with a JSON API
153 lines (152 loc) • 5.99 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const platform_1 = require("../platform");
const Utils_1 = require("../Utils");
const jxt_1 = require("../jxt");
const xrd_1 = tslib_1.__importDefault(require("../protocol/xrd"));
class NetworkDiscovery {
constructor() {
this.hostMetaCache = new Map();
this.hostMetaTTL = 30000;
this.resolver = (0, platform_1.createResolver)();
this.registry = new jxt_1.Registry();
this.registry.define(xrd_1.default);
}
async getHostMeta(domain) {
const cached = this.hostMetaCache.get(domain);
if (cached) {
if (cached.created + this.hostMetaTTL < Date.now()) {
return cached.hostmeta;
}
else {
this.hostMetaCache.delete(domain);
}
}
const hostmeta = (0, Utils_1.promiseAny)([
fetch(`https://${domain}/.well-known/host-meta.json`).then(async (res) => {
if (!res.ok) {
throw new Error('could-not-fetch-json');
}
return res.json();
}),
fetch(`https://${domain}/.well-known/host-meta`).then(async (res) => {
if (!res.ok) {
throw new Error('could-not-fetch-xml');
}
const data = await res.text();
const xml = (0, jxt_1.parse)(data);
if (xml) {
return this.registry.import(xml);
}
else {
throw new Error('could-not-import-xml');
}
})
]);
this.hostMetaCache.set(domain, { created: Date.now(), hostmeta });
hostmeta.catch(() => {
this.hostMetaCache.delete(domain);
});
return hostmeta;
}
async resolveTXT(domain) {
var _a, _b;
return (_b = (_a = this.resolver) === null || _a === void 0 ? void 0 : _a.resolveTxt(domain)) !== null && _b !== void 0 ? _b : [];
}
async resolve(domain, defaultPort, opts = {}) {
if (!this.resolver) {
return [];
}
let candidates = [];
let allowFallback = true;
if (opts.srvType) {
const srvResults = await this.resolveWeightedSRV(domain, opts.srvType, opts.srvTypeSecure);
allowFallback = srvResults.allowFallback;
candidates = srvResults.records.map(record => ({
host: record.name,
port: record.port,
secure: record.secure
}));
}
if (allowFallback) {
candidates.push({ host: domain, port: defaultPort });
}
return candidates;
}
async resolveWeightedSRV(domain, srvType, srvTypeSecure) {
const [records, secureRecords] = await Promise.all([
this.resolveSRV(domain, srvType),
srvTypeSecure
? this.resolveSRV(domain, srvTypeSecure, true)
: Promise.resolve({ records: [], allowFallback: false })
]);
const allRecords = [...records.records, ...secureRecords.records];
const priorities = new Map();
let id = 0;
for (const record of allRecords) {
record.id = id++;
record.runningSum = 0;
if (!priorities.has(record.priority)) {
priorities.set(record.priority, []);
}
const priorityGroup = priorities.get(record.priority);
priorityGroup.push(record);
}
const weightRecords = (unweightedRecords) => {
const sorted = [];
while (sorted.length < unweightedRecords.length) {
const ordered = (0, Utils_1.shuffle)(unweightedRecords.filter(record => record.weight === 0 && !record.used));
const unordered = (0, Utils_1.shuffle)(unweightedRecords.filter(record => {
return record.weight !== 0 && !record.used;
}));
let weightSum = 0;
for (const record of unordered) {
weightSum += record.weight;
record.runningSum = weightSum;
ordered.push(record);
}
const selector = Math.floor(Math.random() * (weightSum + 1));
for (const record of ordered) {
if (record.runningSum >= selector) {
record.used = true;
sorted.push(record);
break;
}
}
}
return sorted;
};
let sortedRecords = [];
for (const priority of Array.from(priorities.keys()).sort((a, b) => a < b ? -1 : a > b ? 1 : 0)) {
const priorityGroup = priorities.get(priority);
sortedRecords = sortedRecords.concat(weightRecords(priorityGroup));
}
return {
records: sortedRecords,
allowFallback: records.allowFallback
};
}
async resolveSRV(domain, srvType, secure) {
var _a, _b;
try {
const records = (_b = (await ((_a = this.resolver) === null || _a === void 0 ? void 0 : _a.resolveSrv(`${srvType}.${domain}`)))) !== null && _b !== void 0 ? _b : [];
if (records.length === 1 && (records[0].name === '.' || records[0].name === '')) {
return { records: [], allowFallback: false };
}
return {
records: records
.map((record) => ({ secure, ...record }))
.filter((record) => record.name !== '' && record.name !== '.'),
allowFallback: false
};
}
catch (_c) {
return {
records: [],
allowFallback: true
};
}
}
}
exports.default = NetworkDiscovery;