Add page-view analytics, product counters, SSL expiry, and business renewal.

Auto-count storefront page views with 6-month event retention and lifetime counters, track product visit/cart/sale stats, persist SSL cert expiry with daily probes, and support owner titles plus annual contract renewal.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-26 10:13:06 +03:30
co-authored by Cursor
parent 6cd0b009ef
commit 92601ae729
39 changed files with 1013 additions and 198 deletions
+67 -21
View File
@@ -2,8 +2,13 @@ import * as tls from 'tls';
const DEFAULT_TIMEOUT_MS = 4000;
export type TlsProbeResult = {
ok: boolean;
/** Certificate notAfter when the probe succeeded and a peer cert was present. */
expiresAt: Date | null;
};
function peerMatchesHost(host: string, socket: tls.TLSSocket): boolean {
// detailed=true so SAN is present for checkServerIdentity
const cert = socket.getPeerCertificate(true);
if (!cert || Object.keys(cert).length === 0) {
return false;
@@ -16,26 +21,43 @@ function peerMatchesHost(host: string, socket: tls.TLSSocket): boolean {
}
}
function readCertExpiry(socket: tls.TLSSocket): Date | null {
const cert = socket.getPeerCertificate(true);
if (!cert?.valid_to) return null;
const expiresAt = new Date(cert.valid_to);
return Number.isNaN(expiresAt.getTime()) ? null : expiresAt;
}
/** Earliest non-null expiry among successful probe results (or null). */
export function earliestTlsExpiry(
...results: Array<TlsProbeResult | null | undefined>
): Date | null {
const times = results
.filter((r): r is TlsProbeResult => !!r?.ok && !!r.expiresAt)
.map((r) => r.expiresAt!.getTime());
if (times.length === 0) return null;
return new Date(Math.min(...times));
}
/**
* Live TLS check: connect to host:443 with SNI and require a trusted cert
* whose identity matches the hostname (not merely a valid LE chain for another name).
* Returns false on timeout, DNS failure, or cert/hostname errors.
*/
export function probeTlsHost(
export function probeTlsHostDetailed(
hostRaw: string,
timeoutMs = DEFAULT_TIMEOUT_MS,
): Promise<boolean> {
): Promise<TlsProbeResult> {
const host = hostRaw.trim().toLowerCase();
if (!host || host.includes(':') || host.endsWith('.local')) {
return Promise.resolve(false);
return Promise.resolve({ ok: false, expiresAt: null });
}
return new Promise((resolve) => {
let settled = false;
const finish = (ok: boolean) => {
const finish = (result: TlsProbeResult) => {
if (settled) return;
settled = true;
resolve(ok);
resolve(result);
};
let socket: tls.TLSSocket;
@@ -49,35 +71,44 @@ export function probeTlsHost(
timeout: timeoutMs,
},
() => {
// authorized=true alone is not enough: a default vhost can present
// another site's valid cert (ERR_CERT_COMMON_NAME_INVALID in browsers).
const ok = socket.authorized === true && peerMatchesHost(host, socket);
const ok =
socket.authorized === true && peerMatchesHost(host, socket);
const expiresAt = ok ? readCertExpiry(socket) : null;
socket.destroy();
finish(ok);
finish({ ok, expiresAt });
},
);
} catch {
finish(false);
finish({ ok: false, expiresAt: null });
return;
}
socket.on('error', () => {
socket.destroy();
finish(false);
finish({ ok: false, expiresAt: null });
});
socket.on('timeout', () => {
socket.destroy();
finish(false);
finish({ ok: false, expiresAt: null });
});
});
}
/** Probe many hosts in parallel (chunked); returns Map host → ssl ok. */
export async function probeTlsHosts(
/** Boolean wrapper for callers that only need ok/fail. */
export async function probeTlsHost(
hostRaw: string,
timeoutMs = DEFAULT_TIMEOUT_MS,
): Promise<boolean> {
const result = await probeTlsHostDetailed(hostRaw, timeoutMs);
return result.ok;
}
/** Probe many hosts in parallel (chunked); returns Map host → detailed result. */
export async function probeTlsHostsDetailed(
hosts: Array<string | null | undefined>,
chunkSize = 10,
): Promise<Map<string, boolean>> {
): Promise<Map<string, TlsProbeResult>> {
const unique = [
...new Set(
hosts
@@ -86,16 +117,31 @@ export async function probeTlsHosts(
),
];
const results = new Map<string, boolean>();
const results = new Map<string, TlsProbeResult>();
for (let i = 0; i < unique.length; i += chunkSize) {
const chunk = unique.slice(i, i + chunkSize);
const probed = await Promise.all(
chunk.map(async (host) => [host, await probeTlsHost(host)] as const),
chunk.map(
async (host) => [host, await probeTlsHostDetailed(host)] as const,
),
);
for (const [host, ok] of probed) {
results.set(host, ok);
for (const [host, result] of probed) {
results.set(host, result);
}
}
return results;
}
/** Probe many hosts; returns Map host → ssl ok. */
export async function probeTlsHosts(
hosts: Array<string | null | undefined>,
chunkSize = 10,
): Promise<Map<string, boolean>> {
const detailed = await probeTlsHostsDetailed(hosts, chunkSize);
const results = new Map<string, boolean>();
for (const [host, result] of detailed) {
results.set(host, result.ok);
}
return results;
}