|
@@ -1,24 +1,32 @@
|
|
|
import * as fs from 'fs';
|
|
import * as fs from 'fs';
|
|
|
|
|
+import * as path from 'path';
|
|
|
|
|
+import * as os from 'os';
|
|
|
import { upsertEnvLine } from './cookie-utils';
|
|
import { upsertEnvLine } from './cookie-utils';
|
|
|
import type { CredentialsPayload, OutputMode } from './types';
|
|
import type { CredentialsPayload, OutputMode } from './types';
|
|
|
|
|
|
|
|
|
|
+export function smsCodePath(): string {
|
|
|
|
|
+ const dataHome = process.env.ROOM_CAL_DATA_HOME ?? path.join(os.homedir(), '.local', 'room_cal');
|
|
|
|
|
+ return path.join(dataHome, 'sms_code.txt');
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function clearSmsCode(): void {
|
|
|
|
|
+ const p = smsCodePath();
|
|
|
|
|
+ if (fs.existsSync(p)) fs.unlinkSync(p);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// TODO: lower timeout for release (120s is generous for human-in-the-loop dev)
|
|
|
export async function fetchSmsCode(timeoutSeconds = 120): Promise<string | null> {
|
|
export async function fetchSmsCode(timeoutSeconds = 120): Promise<string | null> {
|
|
|
- const baseUrl = process.env.ROOM_CAL_API_URL;
|
|
|
|
|
- if (!baseUrl) return null;
|
|
|
|
|
- const url = `${baseUrl.replace(/\/$/, '')}/sms/code?timeout=${timeoutSeconds}`;
|
|
|
|
|
- const headers: Record<string, string> = {};
|
|
|
|
|
- const secret = process.env.SMS_GATEWAY_SECRET;
|
|
|
|
|
- if (secret) headers['X-SMS-Secret'] = secret;
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await fetch(url, { headers, signal: AbortSignal.timeout((timeoutSeconds + 5) * 1000) });
|
|
|
|
|
- if (res.status === 404) return null;
|
|
|
|
|
- if (!res.ok) throw new Error(`GET /sms/code → ${res.status}`);
|
|
|
|
|
- const data = await res.json() as { code: string };
|
|
|
|
|
- return data.code;
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('[sms] fetchSmsCode error:', err);
|
|
|
|
|
- return null;
|
|
|
|
|
|
|
+ const codePath = smsCodePath();
|
|
|
|
|
+ const deadline = Date.now() + timeoutSeconds * 1000;
|
|
|
|
|
+ while (Date.now() < deadline) {
|
|
|
|
|
+ if (fs.existsSync(codePath)) {
|
|
|
|
|
+ const code = fs.readFileSync(codePath, 'utf8').trim();
|
|
|
|
|
+ fs.unlinkSync(codePath);
|
|
|
|
|
+ if (code) return code;
|
|
|
|
|
+ }
|
|
|
|
|
+ await new Promise(r => setTimeout(r, 1000));
|
|
|
}
|
|
}
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export async function putCredentials(service: string, credentials: CredentialsPayload): Promise<void> {
|
|
export async function putCredentials(service: string, credentials: CredentialsPayload): Promise<void> {
|