mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 12:28:21 +00:00
fix issue, add transit flow, fix cancellation
This commit is contained in:
@@ -35,9 +35,24 @@ describe("isDomesticPhone", () => {
|
||||
(phone) => expect(isDomesticPhone(phone)).toBe(true),
|
||||
);
|
||||
|
||||
it.each(["+14155550123", "+447911123456", "0712345678", "+2519866", "12345"])(
|
||||
"rejects non-domestic or malformed %s",
|
||||
(phone) => expect(isDomesticPhone(phone)).toBe(false),
|
||||
// Djibouti is the line's other end: the gateway reaches its 77x mobiles.
|
||||
it.each(["+25377123456", "25377123456", "77123456"])(
|
||||
"accepts Djibouti mobile form %s",
|
||||
(phone) => expect(isDomesticPhone(phone)).toBe(true),
|
||||
);
|
||||
|
||||
it.each([
|
||||
"+14155550123",
|
||||
"+447911123456",
|
||||
"0712345678",
|
||||
"+2519866",
|
||||
"12345",
|
||||
// Djibouti fixed line (2x) — valid number, not a mobile the gateway serves.
|
||||
"+25321350000",
|
||||
// Right length, wrong Djibouti prefix.
|
||||
"+25366123456",
|
||||
])("rejects unreachable or malformed %s", (phone) =>
|
||||
expect(isDomesticPhone(phone)).toBe(false),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -42,20 +42,42 @@ function normalizePhone(rawPhone: string): string {
|
||||
if (digits.startsWith("+")) return digits;
|
||||
const bare = digits.replace(/^0+/, "");
|
||||
if (/^251\d{9}$/.test(digits)) return `+${digits}`;
|
||||
if (/^253\d{8}$/.test(digits)) return `+${digits}`;
|
||||
if (/^9\d{8}$|^7\d{8}$/.test(bare)) return `+251${bare}`;
|
||||
// Djibouti mobiles are 8 digits starting 77 and have no trunk prefix, so a
|
||||
// bare "77…" is unambiguous — it cannot be an Ethiopian local number, which
|
||||
// is always 9 digits after the trunk zero.
|
||||
if (/^77\d{6}$/.test(bare)) return `+253${bare}`;
|
||||
// Unknown shape (foreign number, already-clean intl without +) — prefix + if
|
||||
// it looks like a full international number, else leave as typed.
|
||||
return digits.length >= 11 ? `+${digits}` : raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a phone is an Ethiopian mobile the SMS gateway can actually reach —
|
||||
* the carrier integration is domestic-only, so a send to anything else is
|
||||
* queued and silently lost. Callers use this to fall back to email instead of
|
||||
* pretending an SMS is on its way.
|
||||
* Mobile ranges the SMS gateway is contracted to reach, as E.164 patterns.
|
||||
*
|
||||
* The gateway itself is opaque from here — `SmsClientService` publishes to
|
||||
* RabbitMQ and the carrier sits several hops downstream — so this list is a
|
||||
* policy statement, not a capability probe: a number outside it is treated as
|
||||
* unreachable and callers fall back to email rather than promising an SMS that
|
||||
* would be queued and silently dropped.
|
||||
*
|
||||
* - Ethiopia: `+2519…` mobiles only. `+2517…` is deliberately absent; it parses
|
||||
* as a valid ET number but is not a range this gateway delivers to.
|
||||
* - Djibouti: `+25377…`, the country's only mobile range (2x is fixed-line).
|
||||
*/
|
||||
const REACHABLE_MOBILE_PATTERNS = [/^\+2519\d{8}$/, /^\+25377\d{6}$/];
|
||||
|
||||
/**
|
||||
* Whether a phone sits in a mobile range the SMS gateway can actually reach.
|
||||
*
|
||||
* Named "domestic" for the Ethiopian-only era this predates; it now covers both
|
||||
* countries the railway runs through. Callers use it to fall back to email
|
||||
* instead of pretending an SMS is on its way.
|
||||
*/
|
||||
export function isDomesticPhone(rawPhone: string): boolean {
|
||||
return /^\+2519\d{8}$/.test(normalizePhone(rawPhone));
|
||||
const normalized = normalizePhone(rawPhone);
|
||||
return REACHABLE_MOBILE_PATTERNS.some((p) => p.test(normalized));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +121,7 @@ export class OtpService {
|
||||
private readonly otpRepository: OtpRepository,
|
||||
private readonly notifications: NotificationsService,
|
||||
private readonly emailClient: EmailClientService,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Generate OTP
|
||||
@@ -197,8 +219,10 @@ export class OtpService {
|
||||
|
||||
for (const outcome of outcomes) {
|
||||
this.logger.log(
|
||||
`otp.dispatch channel=${outcome.channel} target=${label} queued=${outcome.queued
|
||||
} latencyMs=${Date.now() - startedAt}${outcome.error ? ` error=${outcome.error}` : ""
|
||||
`otp.dispatch channel=${outcome.channel} target=${label} queued=${
|
||||
outcome.queued
|
||||
} latencyMs=${Date.now() - startedAt}${
|
||||
outcome.error ? ` error=${outcome.error}` : ""
|
||||
}`,
|
||||
);
|
||||
}
|
||||
@@ -222,7 +246,8 @@ export class OtpService {
|
||||
// user who never receives a code — indistinguishable from carrier loss,
|
||||
// and the misleading success response makes it look like our side worked.
|
||||
this.logger.error(
|
||||
`otp.dispatch.dropped channels=${channels.join("+")} target=${label} rabbitmqEnabled=${process.env.RABBITMQ_ENABLED ?? "unset"
|
||||
`otp.dispatch.dropped channels=${channels.join("+")} target=${label} rabbitmqEnabled=${
|
||||
process.env.RABBITMQ_ENABLED ?? "unset"
|
||||
} — no transport reported hand-off; no code will arrive for this send`,
|
||||
);
|
||||
}
|
||||
@@ -247,7 +272,8 @@ export class OtpService {
|
||||
// Log the real cause (DB/SMS/email failure) with its stack so a deployed
|
||||
// "Failed to send OTP" 400 is diagnosable from the API logs, not opaque.
|
||||
this.logger.error(
|
||||
`otp.dispatch.failed channels=${channels.join("+")} target=${label} latencyMs=${Date.now() - startedAt
|
||||
`otp.dispatch.failed channels=${channels.join("+")} target=${label} latencyMs=${
|
||||
Date.now() - startedAt
|
||||
}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
error instanceof Error ? error.stack : undefined,
|
||||
);
|
||||
@@ -330,8 +356,9 @@ export class OtpService {
|
||||
) {
|
||||
const line = `otp.verify channels=${channelsOf(target).join(
|
||||
"+",
|
||||
)} target=${this.targetLabel(target)} mode=${mode} result=${result}${detail ? ` ${detail}` : ""
|
||||
}`;
|
||||
)} target=${this.targetLabel(target)} mode=${mode} result=${result}${
|
||||
detail ? ` ${detail}` : ""
|
||||
}`;
|
||||
if (result === "ok") this.logger.log(line);
|
||||
else this.logger.warn(line);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user