fix(sms): stop sending platform to the relay gateway

With platform "kave" (Kavenegar) the gateway answered "cURL library is not
loaded" and nothing was delivered. The body is now { phone, host, text } and
the gateway picks the line itself. SMS_RELAY_PLATFORM is removed from the
driver, docker-compose and the env examples.

Tests: sms.service.spec updated. 150 tests pass.

پیامک بدون انتخاب خط کاوه‌نگار فرستاده می‌شود و سرویس پیامک خودش خط را انتخاب می‌کند.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
s.esmaeili
2026-10-10 16:34:57 +03:30
co-authored by Cursor
parent c34bf80e6f
commit 797d030e67
7 changed files with 10 additions and 30 deletions
-2
View File
@@ -130,8 +130,6 @@ SMS_RELAY_SECRET=
# The "host" field. Blank sends this machine's first external IPv4 — inside
# Docker that is the container's address.
SMS_RELAY_HOST=
# "kave" (default, Kavenegar) or "default" to let the gateway pick the line.
SMS_RELAY_PLATFORM=
# trez — only used with SMS_PROVIDER=trez. Never commit real values.
# Override only for staging; defaults to the provider's live endpoint.
SMS_TREZ_URL=
+2 -2
View File
@@ -208,8 +208,8 @@ container.
`SMS_PROVIDER`: `relay` (default), `trez`, or `none` (D26).
- **relay**: `SMS_RELAY_URL`, `SMS_RELAY_SECRET` (both have built-in
defaults), `SMS_RELAY_HOST` (blank = the machine's first external IPv4),
`SMS_RELAY_PLATFORM` (`kave` default, or `default` to let the gateway pick).
defaults), `SMS_RELAY_HOST` (blank = the machine's first external IPv4).
No `platform` is sent; the gateway picks the line.
- **trez**: `SMS_TREZ_URL`, `SMS_TREZ_USERNAME`, `SMS_TREZ_PASSWORD`,
`SMS_TREZ_SENDER_LINE`.
-2
View File
@@ -36,8 +36,6 @@ SMS_RELAY_SECRET=
# Docker it is the container's 172.x), the public IP is looked up once at boot.
# Setting it explicitly on the server is the surest; the boot log shows the value.
SMS_RELAY_HOST=
# "kave" (default, Kavenegar) or "default" to let the gateway pick the line.
SMS_RELAY_PLATFORM=
# trez — only used with SMS_PROVIDER=trez.
# Defaults to the reference endpoint when unset; override for staging.
SMS_TREZ_URL=
+2 -6
View File
@@ -87570,7 +87570,6 @@ var require_relay_driver = __commonJS({
var sms_driver_1 = require_sms_driver();
var DEFAULT_RELAY_URL = "http://185.231.112.101/api/createsms";
var DEFAULT_RELAY_SECRET = "f21debfb-83ac-4624-a2f4-f08375043181";
var DEFAULT_RELAY_PLATFORM = "kave";
var REQUEST_TIMEOUT_MS = 15e3;
function serverIpv4() {
for (const addresses of Object.values((0, node_os_1.networkInterfaces)())) {
@@ -87609,13 +87608,10 @@ var require_relay_driver = __commonJS({
publicIpv4 = null;
}
function relayConfig() {
const platform = (0, env_12.envOr)("SMS_RELAY_PLATFORM", DEFAULT_RELAY_PLATFORM);
return {
url: (0, env_12.envOr)("SMS_RELAY_URL", DEFAULT_RELAY_URL),
secret: (0, env_12.envOr)("SMS_RELAY_SECRET", DEFAULT_RELAY_SECRET),
host: (0, env_12.envOrNull)("SMS_RELAY_HOST") ?? publicIpv4 ?? serverIpv4(),
// "default" sends null, so the gateway chooses the line itself.
platform: platform.toLowerCase() === "default" ? null : platform
host: (0, env_12.envOrNull)("SMS_RELAY_HOST") ?? publicIpv4 ?? serverIpv4()
};
}
function relayRejection(body) {
@@ -87649,7 +87645,7 @@ var require_relay_driver = __commonJS({
async deliver(mobile, text) {
await resolveRelayHost();
const c = relayConfig();
const reply = await (0, sms_driver_1.postWithTimeout)(c.url, { "Content-Type": "application/json", secret_key: c.secret }, JSON.stringify({ phone: mobile, host: c.host, text, platform: c.platform }), REQUEST_TIMEOUT_MS);
const reply = await (0, sms_driver_1.postWithTimeout)(c.url, { "Content-Type": "application/json", secret_key: c.secret }, JSON.stringify({ phone: mobile, host: c.host, text }), REQUEST_TIMEOUT_MS);
if (!reply.ok)
return { ok: false, response: reply.body, error: `http-${reply.status}` };
const rejection = relayRejection(reply.body);
@@ -9,11 +9,12 @@ import { SmsDriver, postWithTimeout } from './sms-driver';
*
* Shape, from the owner's PHP client:
* * POST JSON, 15s timeout, auth header `secret_key`
* * body: { phone, host, text, platform }
* * body: { phone, host, text }
* * `host` identifies the sending server: a configured value, else the
* server's own IP (`gethostbyname(gethostname())`)
* * `platform: "kave"` routes through Kavenegar; null lets the gateway pick.
* The owner wants every message through Kavenegar.
* * no `platform`: the gateway picks the line itself. Sending
* `platform: "kave"` (Kavenegar) failed on the gateway with
* "cURL library is not loaded".
*
* The secret is in source on the owner's decision (D26): the gateway has a
* second verification factor held elsewhere. `SMS_RELAY_SECRET` overrides it.
@@ -24,8 +25,6 @@ import { SmsDriver, postWithTimeout } from './sms-driver';
const DEFAULT_RELAY_URL = 'http://185.231.112.101/api/createsms';
const DEFAULT_RELAY_SECRET = 'f21debfb-83ac-4624-a2f4-f08375043181';
const DEFAULT_RELAY_PLATFORM = 'kave';
/** `Http::timeout(15)` in the owner's client. */
const REQUEST_TIMEOUT_MS = 15_000;
@@ -79,13 +78,10 @@ export function resetRelayHostCache() {
}
export function relayConfig() {
const platform = envOr('SMS_RELAY_PLATFORM', DEFAULT_RELAY_PLATFORM);
return {
url: envOr('SMS_RELAY_URL', DEFAULT_RELAY_URL),
secret: envOr('SMS_RELAY_SECRET', DEFAULT_RELAY_SECRET),
host: envOrNull('SMS_RELAY_HOST') ?? publicIpv4 ?? serverIpv4(),
// "default" sends null, so the gateway chooses the line itself.
platform: platform.toLowerCase() === 'default' ? null : platform,
};
}
@@ -137,7 +133,7 @@ export const relayDriver: SmsDriver = {
const reply = await postWithTimeout(
c.url,
{ 'Content-Type': 'application/json', secret_key: c.secret },
JSON.stringify({ phone: mobile, host: c.host, text, platform: c.platform }),
JSON.stringify({ phone: mobile, host: c.host, text }),
REQUEST_TIMEOUT_MS,
);
+1 -8
View File
@@ -75,12 +75,11 @@ describe('SMS configuration', () => {
});
describe('relay', () => {
it('works out of the box, sending through Kavenegar', () => {
it('works out of the box', () => {
process.env.SMS_RELAY_HOST = '10.0.0.5';
const c = relayConfig();
expect(c.url).toBe('http://185.231.112.101/api/createsms');
expect(c.secret).toBeTruthy();
expect(c.platform).toBe('kave');
expect(smsConfigured()).toBe(true);
});
@@ -97,11 +96,6 @@ describe('SMS configuration', () => {
expect(relayConfig().host).toBe('gold.example');
});
it('sends platform null when SMS_RELAY_PLATFORM=default', () => {
process.env.SMS_RELAY_PLATFORM = 'default';
expect(relayConfig().platform).toBeNull();
});
it('is not configured, and warns by name, when no host can be found', () => {
noExternalIp();
expect(smsConfigured()).toBe(false);
@@ -232,7 +226,6 @@ describe('SmsService.send', () => {
phone: '09120000000',
host: '10.0.0.5',
text: 'سلام',
platform: 'kave',
});
expect(create).toHaveBeenCalledWith({
data: expect.objectContaining({ recipients: '09120000000', body: 'سلام' }),
-1
View File
@@ -56,7 +56,6 @@ services:
- SMS_RELAY_URL=${SMS_RELAY_URL:-}
- SMS_RELAY_SECRET=${SMS_RELAY_SECRET:-}
- SMS_RELAY_HOST=${SMS_RELAY_HOST:-}
- SMS_RELAY_PLATFORM=${SMS_RELAY_PLATFORM:-}
- SMS_TREZ_URL=${SMS_TREZ_URL:-}
- SMS_TREZ_USERNAME=${SMS_TREZ_USERNAME:-}
- SMS_TREZ_PASSWORD=${SMS_TREZ_PASSWORD:-}