refactor(acme-client): 将acme-client改造成ts包并优化项目结构

重构acme-client模块,将原有JavaScript代码迁移至TypeScript
添加类型定义文件(.d.ts)和类型检查
更新构建配置和脚本以支持TypeScript编译
优化项目目录结构和模块导出方式
更新相关依赖和开发工具配置
This commit is contained in:
xiaojunnuo
2026-05-05 19:17:44 +08:00
parent e0143fa540
commit 930aa355e8
25 changed files with 450 additions and 32 deletions
@@ -1,3 +1,4 @@
// @ts-nocheck
/**
* ACME API client
*/
@@ -1,3 +1,4 @@
// @ts-nocheck
/**
* ACME auto helper
*/
@@ -1,3 +1,4 @@
// @ts-nocheck
/**
* Axios instance
*/
@@ -1,3 +1,4 @@
// @ts-nocheck
/**
* ACME client
*
@@ -570,7 +571,7 @@ class AcmeClient {
* ```
*/
async waitForValidStatus(item,d) {
async waitForValidStatus(item, d?) {
if (!item.url) {
throw new Error(`[${d}] Unable to verify status of item, URL not found`);
}
@@ -1,3 +1,4 @@
// @ts-nocheck
/**
* Legacy node-forge crypto interface
*
@@ -112,7 +113,7 @@ function parseDomains(obj) {
* ```
*/
export async function createPrivateKey(size = 2048) {
export async function createPrivateKey(size = 2048): Promise<Buffer> {
const keyPair = await generateKeyPair({ bits: size });
const pemKey = forge.pki.privateKeyToPem(keyPair.privateKey);
return Buffer.from(pemKey);
@@ -131,7 +132,7 @@ export async function createPrivateKey(size = 2048) {
* ```
*/
export const createPublicKey = async (key) => {
export const createPublicKey = async (key): Promise<Buffer> => {
const privateKey = forge.pki.privateKeyFromPem(key);
const publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e);
const pemKey = forge.pki.publicKeyToPem(publicKey);
@@ -174,7 +175,7 @@ export const splitPemChain = (str) => forge.pem.decode(str).map(forge.pem.encode
* ```
*/
export const getModulus = async (input) => {
export const getModulus = async (input): Promise<Buffer> => {
if (!Buffer.isBuffer(input)) {
input = Buffer.from(input);
}
@@ -197,7 +198,7 @@ export const getModulus = async (input) => {
* ```
*/
export const getPublicExponent = async (input) => {
export const getPublicExponent = async (input): Promise<Buffer> => {
if (!Buffer.isBuffer(input)) {
input = Buffer.from(input);
}
@@ -1,3 +1,4 @@
// @ts-nocheck
/**
* Native Node.js crypto interface
*
@@ -67,7 +68,7 @@ function getKeyInfo(keyPem) {
* ```
*/
export async function createPrivateRsaKey(modulusLength = 2048, encodingType = 'pkcs8') {
export async function createPrivateRsaKey(modulusLength = 2048, encodingType = 'pkcs8'): Promise<Buffer> {
const pair = await generateKeyPair('rsa', {
modulusLength,
privateKeyEncoding: {
@@ -105,7 +106,7 @@ export const createPrivateKey = createPrivateRsaKey;
* ```
*/
export const createPrivateEcdsaKey = async (namedCurve = 'P-256', encodingType = 'pkcs8') => {
export const createPrivateEcdsaKey = async (namedCurve = 'P-256', encodingType = 'pkcs8'): Promise<Buffer> => {
const pair = await generateKeyPair('ec', {
namedCurve,
privateKeyEncoding: {
@@ -129,7 +130,7 @@ export const createPrivateEcdsaKey = async (namedCurve = 'P-256', encodingType =
* ```
*/
export const getPublicKey = (keyPem) => {
export const getPublicKey = (keyPem): Buffer => {
const info = getKeyInfo(keyPem);
const publicKey = info.publicKey.export({
@@ -1,3 +1,4 @@
// @ts-nocheck
export class CancelError extends Error {
constructor(message) {
super(message);
@@ -1,3 +1,4 @@
// @ts-nocheck
/**
* ACME HTTP client
*/
@@ -0,0 +1,19 @@
import assert from "node:assert/strict";
import { directory, getAllSslProviderDomains, getDirectoryUrl } from "./index.js";
declare const describe: any;
declare const it: any;
describe("directory helpers", () => {
it("selects the provider specific directory endpoint", () => {
assert.equal(getDirectoryUrl({ sslProvider: "sslcom", pkType: "ec" }), directory.sslcom.ec);
assert.equal(getDirectoryUrl({ sslProvider: "letsencrypt", pkType: "rsa" }), directory.letsencrypt.production);
});
it("includes configured provider domains", () => {
const domains = getAllSslProviderDomains();
assert.ok(domains.includes("acme.litessl.com"));
assert.ok(domains.includes("acme.ssl.com"));
});
});
@@ -1,8 +1,9 @@
// @ts-nocheck
/**
* acme-client
*/
import AcmeClinet from './client.js'
export const Client = AcmeClinet
export { default as Client } from './client.js'
export type * from './types.js'
/**
* Directory URLs
@@ -103,4 +104,4 @@ export * from './logger.js'
export * from './verify.js'
export * from './error.js'
export * from './util.js'
export * from './util.js'
@@ -1,3 +1,4 @@
// @ts-nocheck
/**
* ACME logger
*/
+123
View File
@@ -0,0 +1,123 @@
/**
* Account
*
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.2
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.3
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.3.2
*/
export interface Account {
status: "valid" | "deactivated" | "revoked";
orders: string;
contact?: string[];
termsOfServiceAgreed?: boolean;
externalAccountBinding?: object;
}
export interface AccountCreateRequest {
contact?: string[];
termsOfServiceAgreed?: boolean;
onlyReturnExisting?: boolean;
externalAccountBinding?: object;
}
export interface AccountUpdateRequest {
status?: string;
contact?: string[];
termsOfServiceAgreed?: boolean;
}
/**
* Order
*
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.3
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.4
*/
export interface Order {
status: "pending" | "ready" | "processing" | "valid" | "invalid";
identifiers: Identifier[];
authorizations: string[];
finalize: string;
expires?: string;
notBefore?: string;
notAfter?: string;
error?: object;
certificate?: string;
}
export interface OrderCreateRequest {
identifiers: Identifier[];
notBefore?: string;
notAfter?: string;
}
/**
* Authorization
*
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.4
*/
export interface Authorization {
identifier: Identifier;
status: "pending" | "valid" | "invalid" | "deactivated" | "expired" | "revoked";
challenges: Challenge[];
expires?: string;
wildcard?: boolean;
}
export interface Identifier {
type: string;
value: string;
}
/**
* Challenge
*
* https://datatracker.ietf.org/doc/html/rfc8555#section-8
* https://datatracker.ietf.org/doc/html/rfc8555#section-8.3
* https://datatracker.ietf.org/doc/html/rfc8555#section-8.4
*/
export interface ChallengeAbstract {
type: string;
url: string;
status: "pending" | "processing" | "valid" | "invalid";
validated?: string;
error?: object;
}
export interface HttpChallenge extends ChallengeAbstract {
type: "http-01";
token: string;
}
export interface DnsChallenge extends ChallengeAbstract {
type: "dns-01";
token: string;
}
export type Challenge = HttpChallenge | DnsChallenge;
/**
* Certificate
*
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.6
*/
export enum CertificateRevocationReason {
Unspecified = 0,
KeyCompromise = 1,
CACompromise = 2,
AffiliationChanged = 3,
Superseded = 4,
CessationOfOperation = 5,
CertificateHold = 6,
RemoveFromCRL = 8,
PrivilegeWithdrawn = 9,
AACompromise = 10,
}
export interface CertificateRevocationRequest {
reason?: CertificateRevocationReason;
}
+131
View File
@@ -0,0 +1,131 @@
import type * as rfc8555 from "./rfc8555.js";
import type { Challenge } from "./rfc8555.js";
export type * from "./rfc8555.js";
export type PrivateKeyBuffer = Buffer;
export type PublicKeyBuffer = Buffer;
export type CertificateBuffer = Buffer;
export type CsrBuffer = Buffer;
export type PrivateKeyString = string;
export type PublicKeyString = string;
export type CertificateString = string;
export type CsrString = string;
export interface Order extends rfc8555.Order {
url: string;
}
export interface Authorization extends rfc8555.Authorization {
url: string;
}
export type UrlMapping = {
enabled: boolean;
mappings: Record<string, string>;
};
export interface ClientExternalAccountBindingOptions {
kid: string;
hmacKey: string;
}
export interface ClientOptions {
sslProvider: string;
directoryUrl: string;
accountKey: PrivateKeyBuffer | PrivateKeyString;
accountUrl?: string;
externalAccountBinding?: ClientExternalAccountBindingOptions;
backoffAttempts?: number;
backoffMin?: number;
backoffMax?: number;
urlMapping?: UrlMapping;
signal?: AbortSignal;
logger?: any;
}
export interface ClientAutoOptions {
csr: CsrBuffer | CsrString;
challengeCreateFn: (
authz: Authorization,
keyAuthorization: (challenge: Challenge) => Promise<string>
) => Promise<{ recordReq?: any; recordRes?: any; dnsProvider?: any; challenge: Challenge; keyAuthorization: string }>;
challengeRemoveFn: (authz: Authorization, challenge: Challenge, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider: any, httpUploader: any) => Promise<any>;
email?: string;
termsOfServiceAgreed?: boolean;
skipChallengeVerification?: boolean;
challengePriority?: string[];
preferredChain?: string;
signal?: AbortSignal;
profile?: string;
waitDnsDiffuseTime?: number;
}
export interface CertificateDomains {
commonName: string;
altNames: string[];
}
export interface CertificateIssuer {
commonName: string;
}
export interface CertificateInfo {
issuer: CertificateIssuer;
domains: CertificateDomains;
notAfter: Date;
notBefore: Date;
}
export interface CsrOptions {
keySize?: number;
commonName?: string;
altNames?: string[];
country?: string;
state?: string;
locality?: string;
organization?: string;
organizationUnit?: string;
emailAddress?: string;
}
export interface RsaPublicJwk {
e: string;
kty: string;
n: string;
}
export interface EcdsaPublicJwk {
crv: string;
kty: string;
x: string;
y: string;
}
export interface CryptoInterface {
createPrivateKey(keySize?: number, encodingType?: string): Promise<PrivateKeyBuffer>;
createPrivateRsaKey(keySize?: number, encodingType?: string): Promise<PrivateKeyBuffer>;
createPrivateEcdsaKey(namedCurve?: "P-256" | "P-384" | "P-521", encodingType?: string): Promise<PrivateKeyBuffer>;
getPublicKey(keyPem: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString): PublicKeyBuffer;
getJwk(keyPem: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString): RsaPublicJwk | EcdsaPublicJwk;
splitPemChain(chainPem: CertificateBuffer | CertificateString): string[];
getPemBodyAsB64u(pem: CertificateBuffer | CertificateString): string;
readCsrDomains(csrPem: CsrBuffer | CsrString): CertificateDomains;
readCertificateInfo(certPem: CertificateBuffer | CertificateString): CertificateInfo;
createCsr(data: CsrOptions, keyPem?: PrivateKeyBuffer | PrivateKeyString, encodingType?: string): Promise<[PrivateKeyBuffer, CsrBuffer]>;
createAlpnCertificate(authz: Authorization, keyAuthorization: string, keyPem?: PrivateKeyBuffer | PrivateKeyString): Promise<[PrivateKeyBuffer, CertificateBuffer]>;
isAlpnCertificateAuthorizationValid(certPem: CertificateBuffer | CertificateString, keyAuthorization: string): boolean;
}
export interface CryptoLegacyInterface {
createPrivateKey(size?: number): Promise<PrivateKeyBuffer>;
createPublicKey(key: PrivateKeyBuffer | PrivateKeyString): Promise<PublicKeyBuffer>;
getPemBody(str: string): string;
splitPemChain(str: string): string[];
getModulus(input: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString | CertificateBuffer | CertificateString | CsrBuffer | CsrString): Promise<Buffer>;
getPublicExponent(input: PrivateKeyBuffer | PrivateKeyString | PublicKeyBuffer | PublicKeyString | CertificateBuffer | CertificateString | CsrBuffer | CsrString): Promise<Buffer>;
readCsrDomains(csr: CsrBuffer | CsrString): Promise<CertificateDomains>;
readCertificateInfo(cert: CertificateBuffer | CertificateString): Promise<CertificateInfo>;
createCsr(data: CsrOptions, key?: PrivateKeyBuffer | PrivateKeyString): Promise<[PrivateKeyBuffer, CsrBuffer]>;
}
@@ -0,0 +1,58 @@
import assert from "node:assert/strict";
import { formatResponseError, parseRetryAfterHeader, retry } from "./util.js";
declare const describe: any;
declare const it: any;
describe("util helpers", () => {
it("parses retry-after values", () => {
assert.equal(parseRetryAfterHeader("120"), 120);
assert.equal(parseRetryAfterHeader("invalid"), 0);
assert.equal(parseRetryAfterHeader("Wed, 21 Oct 2015 07:28:00 GMT"), 0);
});
it("formats response errors without newlines", () => {
const error = formatResponseError({
data: {
error: {
detail: "line 1\nline 2",
},
},
});
assert.equal(error, "line 1line 2");
});
it("retries until success", async () => {
const delays: number[] = [];
const originalSetTimeout = globalThis.setTimeout;
let attempts = 0;
(globalThis as any).setTimeout = (fn: (...args: any[]) => void, delay?: number) => {
delays.push(Number(delay));
return originalSetTimeout(fn, 0);
};
try {
const result = await retry(
async () => {
attempts += 1;
if (attempts < 3) {
throw new Error(`boom-${attempts}`);
}
return "ok";
},
{ attempts: 3, min: 10, max: 20 },
() => {}
);
assert.equal(result, "ok");
assert.equal(attempts, 3);
assert.deepEqual(delays, [10, 20]);
} finally {
(globalThis as any).setTimeout = originalSetTimeout;
}
});
});
@@ -1,3 +1,4 @@
// @ts-nocheck
/**
* Utility methods
*/
@@ -1,3 +1,4 @@
// @ts-nocheck
/**
* ACME challenge verification
*/
@@ -1,3 +1,4 @@
// @ts-nocheck
export async function wait(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);