90 lines
2.7 KiB
JavaScript
90 lines
2.7 KiB
JavaScript
import { execSync } from 'node:child_process';
|
|
|
|
const API = process.env.SMOKE_API ?? 'http://localhost:3000/api/v1';
|
|
const DEFAULT_MOCK_CODE = process.env.MOCK_SMS_CODE ?? '123456';
|
|
const REDIS_CONTAINER = process.env.REDIS_CONTAINER ?? 'dukang-redis';
|
|
|
|
function sleep(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function fetchJson(path, options = {}) {
|
|
const res = await fetch(`${API}${path}`, options);
|
|
return res.json();
|
|
}
|
|
|
|
function readSmsCodeFromRedis(phone, scene) {
|
|
const key = `dukang:sms:${scene}:${phone}`;
|
|
try {
|
|
const code = execSync(`docker exec ${REDIS_CONTAINER} redis-cli GET "${key}"`, {
|
|
encoding: 'utf8',
|
|
}).trim();
|
|
if (code && code !== '(nil)') return code;
|
|
} catch {
|
|
/* docker/redis unavailable */
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export async function loadClientConfig() {
|
|
const json = await fetchJson('/common/client-config');
|
|
if (json.code !== 0) throw new Error(`client-config: ${json.message}`);
|
|
return json.data;
|
|
}
|
|
|
|
export async function resolveSmsCode(phone, scene) {
|
|
const cfg = await loadClientConfig();
|
|
if (cfg.mockSms) return DEFAULT_MOCK_CODE;
|
|
|
|
const fromRedis = readSmsCodeFromRedis(phone, scene);
|
|
if (fromRedis) return fromRedis;
|
|
|
|
throw new Error(
|
|
`SMS code not found for ${phone} (${scene}); enable MOCK_SMS or ensure Redis is reachable`,
|
|
);
|
|
}
|
|
|
|
export async function sendSmsOnce(clientApp, sendPath, phone, scene) {
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
'X-Client-App': clientApp,
|
|
};
|
|
const res = await fetch(`${API}${sendPath}`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({ phone, scene }),
|
|
});
|
|
return res.json();
|
|
}
|
|
|
|
export async function sendSmsWithCooldown(clientApp, sendPath, phone, scene) {
|
|
let json = await sendSmsOnce(clientApp, sendPath, phone, scene);
|
|
if (json.code !== 0 && String(json.message).includes('过于频繁')) {
|
|
await sleep(65_000);
|
|
json = await sendSmsOnce(clientApp, sendPath, phone, scene);
|
|
}
|
|
if (json.code !== 0) {
|
|
const existing = readSmsCodeFromRedis(phone, scene);
|
|
if (existing) return { reusedCode: true };
|
|
throw new Error(`${sendPath}: ${json.message}`);
|
|
}
|
|
return json.data;
|
|
}
|
|
|
|
export async function loginWithSms(clientApp, phone, scene, loginPath, sendPath) {
|
|
await sendSmsWithCooldown(clientApp, sendPath, phone, scene);
|
|
const code = await resolveSmsCode(phone, scene);
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
'X-Client-App': clientApp,
|
|
};
|
|
const res = await fetch(`${API}${loginPath}`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({ phone, code }),
|
|
});
|
|
const json = await res.json();
|
|
if (json.code !== 0) throw new Error(`${loginPath}: ${json.message}`);
|
|
return json.data;
|
|
}
|