chore: Lint extensions folder.

This commit is contained in:
cpojer
2026-01-31 22:13:48 +09:00
parent 4f2166c503
commit 230ca789e2
221 changed files with 4006 additions and 1583 deletions
+27 -9
View File
@@ -169,7 +169,9 @@ const voiceCallPlugin = {
if (!validation.valid) {
throw new Error(validation.errors.join("; "));
}
if (runtime) return runtime;
if (runtime) {
return runtime;
}
if (!runtimePromise) {
runtimePromise = createVoiceCallRuntime({
config,
@@ -341,12 +343,16 @@ const voiceCallPlugin = {
switch (params.action) {
case "initiate_call": {
const message = String(params.message || "").trim();
if (!message) throw new Error("message required");
if (!message) {
throw new Error("message required");
}
const to =
typeof params.to === "string" && params.to.trim()
? params.to.trim()
: rt.config.toNumber;
if (!to) throw new Error("to required");
if (!to) {
throw new Error("to required");
}
const result = await rt.manager.initiateCall(to, undefined, {
message,
mode:
@@ -385,7 +391,9 @@ const voiceCallPlugin = {
}
case "end_call": {
const callId = String(params.callId || "").trim();
if (!callId) throw new Error("callId required");
if (!callId) {
throw new Error("callId required");
}
const result = await rt.manager.endCall(callId);
if (!result.success) {
throw new Error(result.error || "end failed");
@@ -394,7 +402,9 @@ const voiceCallPlugin = {
}
case "get_status": {
const callId = String(params.callId || "").trim();
if (!callId) throw new Error("callId required");
if (!callId) {
throw new Error("callId required");
}
const call =
rt.manager.getCall(callId) || rt.manager.getCallByProviderCallId(callId);
return json(call ? { found: true, call } : { found: false });
@@ -405,7 +415,9 @@ const voiceCallPlugin = {
const mode = params?.mode ?? "call";
if (mode === "status") {
const sid = typeof params.sid === "string" ? params.sid.trim() : "";
if (!sid) throw new Error("sid required for status");
if (!sid) {
throw new Error("sid required for status");
}
const call = rt.manager.getCall(sid) || rt.manager.getCallByProviderCallId(sid);
return json(call ? { found: true, call } : { found: false });
}
@@ -414,7 +426,9 @@ const voiceCallPlugin = {
typeof params.to === "string" && params.to.trim()
? params.to.trim()
: rt.config.toNumber;
if (!to) throw new Error("to required for call");
if (!to) {
throw new Error("to required for call");
}
const result = await rt.manager.initiateCall(to, undefined, {
message:
typeof params.message === "string" && params.message.trim()
@@ -447,7 +461,9 @@ const voiceCallPlugin = {
api.registerService({
id: "voicecall",
start: async () => {
if (!config.enabled) return;
if (!config.enabled) {
return;
}
try {
await ensureRuntime();
} catch (err) {
@@ -459,7 +475,9 @@ const voiceCallPlugin = {
}
},
stop: async () => {
if (!runtimePromise) return;
if (!runtimePromise) {
return;
}
try {
const rt = await runtimePromise;
await rt.stop();
+3 -1
View File
@@ -21,7 +21,9 @@ type Logger = {
function resolveMode(input: string): "off" | "serve" | "funnel" {
const raw = input.trim().toLowerCase();
if (raw === "serve" || raw === "off") return raw;
if (raw === "serve" || raw === "off") {
return raw;
}
return "funnel";
}
+12 -4
View File
@@ -72,19 +72,25 @@ function findPackageRoot(startDir: string, name: string): string | null {
if (fs.existsSync(pkgPath)) {
const raw = fs.readFileSync(pkgPath, "utf8");
const pkg = JSON.parse(raw) as { name?: string };
if (pkg.name === name) return dir;
if (pkg.name === name) {
return dir;
}
}
} catch {
// ignore parse errors and keep walking
}
const parent = path.dirname(dir);
if (parent === dir) return null;
if (parent === dir) {
return null;
}
dir = parent;
}
}
function resolveOpenClawRoot(): string {
if (coreRootCache) return coreRootCache;
if (coreRootCache) {
return coreRootCache;
}
const override = process.env.OPENCLAW_ROOT?.trim();
if (override) {
coreRootCache = override;
@@ -128,7 +134,9 @@ async function importCoreModule<T>(relativePath: string): Promise<T> {
}
export async function loadCoreAgentDeps(): Promise<CoreAgentDeps> {
if (coreDepsPromise) return coreDepsPromise;
if (coreDepsPromise) {
return coreDepsPromise;
}
coreDepsPromise = (async () => {
const [
+33 -11
View File
@@ -21,7 +21,9 @@ import { escapeXml, mapVoiceToPolly } from "./voice-mapping.js";
function resolveDefaultStoreBase(config: VoiceCallConfig, storePath?: string): string {
const rawOverride = storePath?.trim() || config.store?.trim();
if (rawOverride) return resolveUserPath(rawOverride);
if (rawOverride) {
return resolveUserPath(rawOverride);
}
const preferred = path.join(os.homedir(), ".openclaw", "voice-calls");
const candidates = [preferred].map((dir) => resolveUserPath(dir));
const existing =
@@ -322,21 +324,27 @@ export class CallManager {
private clearTranscriptWaiter(callId: CallId): void {
const waiter = this.transcriptWaiters.get(callId);
if (!waiter) return;
if (!waiter) {
return;
}
clearTimeout(waiter.timeout);
this.transcriptWaiters.delete(callId);
}
private rejectTranscriptWaiter(callId: CallId, reason: string): void {
const waiter = this.transcriptWaiters.get(callId);
if (!waiter) return;
if (!waiter) {
return;
}
this.clearTranscriptWaiter(callId);
waiter.reject(new Error(reason));
}
private resolveTranscriptWaiter(callId: CallId, transcript: string): void {
const waiter = this.transcriptWaiters.get(callId);
if (!waiter) return;
if (!waiter) {
return;
}
this.clearTranscriptWaiter(callId);
waiter.resolve(transcript);
}
@@ -520,7 +528,9 @@ export class CallManager {
private findCall(callIdOrProviderCallId: string): CallRecord | undefined {
// Try direct lookup by internal callId
const directCall = this.activeCalls.get(callIdOrProviderCallId);
if (directCall) return directCall;
if (directCall) {
return directCall;
}
// Try lookup by providerCallId
return this.getCallByProviderCallId(callIdOrProviderCallId);
@@ -648,13 +658,19 @@ export class CallManager {
const initialMessage =
typeof call.metadata?.initialMessage === "string" ? call.metadata.initialMessage.trim() : "";
if (!initialMessage) return;
if (!initialMessage) {
return;
}
if (!this.provider || !call.providerCallId) return;
if (!this.provider || !call.providerCallId) {
return;
}
// Twilio has provider-specific state for speaking (<Say> fallback) and can
// fail for inbound calls; keep existing Twilio behavior unchanged.
if (this.provider.name === "twilio") return;
if (this.provider.name === "twilio") {
return;
}
void this.speakInitialMessage(call.providerCallId);
}
@@ -740,7 +756,9 @@ export class CallManager {
*/
private transitionState(call: CallRecord, newState: CallState): void {
// No-op for same state or already terminal
if (call.state === newState || TerminalStates.has(call.state)) return;
if (call.state === newState || TerminalStates.has(call.state)) {
return;
}
// Terminal states can always be reached from non-terminal
if (TerminalStates.has(newState)) {
@@ -797,7 +815,9 @@ export class CallManager {
*/
private loadActiveCalls(): void {
const logPath = path.join(this.storePath, "calls.jsonl");
if (!fs.existsSync(logPath)) return;
if (!fs.existsSync(logPath)) {
return;
}
// Read file synchronously and parse lines
const content = fs.readFileSync(logPath, "utf-8");
@@ -807,7 +827,9 @@ export class CallManager {
const callMap = new Map<CallId, CallRecord>();
for (const line of lines) {
if (!line.trim()) continue;
if (!line.trim()) {
continue;
}
try {
const call = CallRecordSchema.parse(JSON.parse(line));
callMap.set(call.callId, call);
+13 -6
View File
@@ -1,7 +1,6 @@
import crypto from "node:crypto";
import type { CallId, CallRecord, CallState, NormalizedEvent } from "../types.js";
import { TerminalStates } from "../types.js";
import type { CallRecord, CallState, NormalizedEvent } from "../types.js";
import type { CallManagerContext } from "./context.js";
import { findCall } from "./lookup.js";
import { addTranscriptEntry, transitionState } from "./state.js";
@@ -81,7 +80,9 @@ function createInboundCall(params: {
}
export function processEvent(ctx: CallManagerContext, event: NormalizedEvent): void {
if (ctx.processedEventIds.has(event.id)) return;
if (ctx.processedEventIds.has(event.id)) {
return;
}
ctx.processedEventIds.add(event.id);
let call = findCall({
@@ -107,7 +108,9 @@ export function processEvent(ctx: CallManagerContext, event: NormalizedEvent): v
event.callId = call.callId;
}
if (!call) return;
if (!call) {
return;
}
if (event.providerCallId && !call.providerCallId) {
call.providerCallId = event.providerCallId;
@@ -160,7 +163,9 @@ export function processEvent(ctx: CallManagerContext, event: NormalizedEvent): v
clearMaxDurationTimer(ctx, call.callId);
rejectTranscriptWaiter(ctx, call.callId, `Call ended: ${event.reason}`);
ctx.activeCalls.delete(call.callId);
if (call.providerCallId) ctx.providerCallIdMap.delete(call.providerCallId);
if (call.providerCallId) {
ctx.providerCallIdMap.delete(call.providerCallId);
}
break;
case "call.error":
@@ -171,7 +176,9 @@ export function processEvent(ctx: CallManagerContext, event: NormalizedEvent): v
clearMaxDurationTimer(ctx, call.callId);
rejectTranscriptWaiter(ctx, call.callId, `Call error: ${event.error}`);
ctx.activeCalls.delete(call.callId);
if (call.providerCallId) ctx.providerCallIdMap.delete(call.providerCallId);
if (call.providerCallId) {
ctx.providerCallIdMap.delete(call.providerCallId);
}
}
break;
}
+3 -1
View File
@@ -24,7 +24,9 @@ export function findCall(params: {
callIdOrProviderCallId: string;
}): CallRecord | undefined {
const directCall = params.activeCalls.get(params.callIdOrProviderCallId);
if (directCall) return directCall;
if (directCall) {
return directCall;
}
return getCallByProviderCallId({
activeCalls: params.activeCalls,
providerCallIdMap: params.providerCallIdMap,
+30 -10
View File
@@ -119,9 +119,15 @@ export async function speak(
text: string,
): Promise<{ success: boolean; error?: string }> {
const call = ctx.activeCalls.get(callId);
if (!call) return { success: false, error: "Call not found" };
if (!ctx.provider || !call.providerCallId) return { success: false, error: "Call not connected" };
if (TerminalStates.has(call.state)) return { success: false, error: "Call has ended" };
if (!call) {
return { success: false, error: "Call not found" };
}
if (!ctx.provider || !call.providerCallId) {
return { success: false, error: "Call not connected" };
}
if (TerminalStates.has(call.state)) {
return { success: false, error: "Call has ended" };
}
try {
transitionState(call, "speaking");
@@ -197,9 +203,15 @@ export async function continueCall(
prompt: string,
): Promise<{ success: boolean; transcript?: string; error?: string }> {
const call = ctx.activeCalls.get(callId);
if (!call) return { success: false, error: "Call not found" };
if (!ctx.provider || !call.providerCallId) return { success: false, error: "Call not connected" };
if (TerminalStates.has(call.state)) return { success: false, error: "Call has ended" };
if (!call) {
return { success: false, error: "Call not found" };
}
if (!ctx.provider || !call.providerCallId) {
return { success: false, error: "Call not connected" };
}
if (TerminalStates.has(call.state)) {
return { success: false, error: "Call has ended" };
}
try {
await speak(ctx, callId, prompt);
@@ -227,9 +239,15 @@ export async function endCall(
callId: CallId,
): Promise<{ success: boolean; error?: string }> {
const call = ctx.activeCalls.get(callId);
if (!call) return { success: false, error: "Call not found" };
if (!ctx.provider || !call.providerCallId) return { success: false, error: "Call not connected" };
if (TerminalStates.has(call.state)) return { success: true };
if (!call) {
return { success: false, error: "Call not found" };
}
if (!ctx.provider || !call.providerCallId) {
return { success: false, error: "Call not connected" };
}
if (TerminalStates.has(call.state)) {
return { success: true };
}
try {
await ctx.provider.hangupCall({
@@ -247,7 +265,9 @@ export async function endCall(
rejectTranscriptWaiter(ctx, callId, "Call ended: hangup-bot");
ctx.activeCalls.delete(callId);
if (call.providerCallId) ctx.providerCallIdMap.delete(call.providerCallId);
if (call.providerCallId) {
ctx.providerCallIdMap.delete(call.providerCallId);
}
return { success: true };
} catch (err) {
+3 -1
View File
@@ -13,7 +13,9 @@ const StateOrder: readonly CallState[] = [
export function transitionState(call: CallRecord, newState: CallState): void {
// No-op for same state or already terminal.
if (call.state === newState || TerminalStates.has(call.state)) return;
if (call.state === newState || TerminalStates.has(call.state)) {
return;
}
// Terminal states can always be reached from non-terminal.
if (TerminalStates.has(newState)) {
+6 -2
View File
@@ -32,7 +32,9 @@ export function loadActiveCallsFromStore(storePath: string): {
const callMap = new Map<CallId, CallRecord>();
for (const line of lines) {
if (!line.trim()) continue;
if (!line.trim()) {
continue;
}
try {
const call = CallRecordSchema.parse(JSON.parse(line));
callMap.set(call.callId, call);
@@ -46,7 +48,9 @@ export function loadActiveCallsFromStore(storePath: string): {
const processedEventIds = new Set<string>();
for (const [callId, call] of callMap) {
if (TerminalStates.has(call.state)) continue;
if (TerminalStates.has(call.state)) {
continue;
}
activeCalls.set(callId, call);
if (call.providerCallId) {
providerCallIdMap.set(call.providerCallId, callId);
+9 -3
View File
@@ -40,7 +40,9 @@ export function startMaxDurationTimer(params: {
export function clearTranscriptWaiter(ctx: CallManagerContext, callId: CallId): void {
const waiter = ctx.transcriptWaiters.get(callId);
if (!waiter) return;
if (!waiter) {
return;
}
clearTimeout(waiter.timeout);
ctx.transcriptWaiters.delete(callId);
}
@@ -51,7 +53,9 @@ export function rejectTranscriptWaiter(
reason: string,
): void {
const waiter = ctx.transcriptWaiters.get(callId);
if (!waiter) return;
if (!waiter) {
return;
}
clearTranscriptWaiter(ctx, callId);
waiter.reject(new Error(reason));
}
@@ -62,7 +66,9 @@ export function resolveTranscriptWaiter(
transcript: string,
): void {
const waiter = ctx.transcriptWaiters.get(callId);
if (!waiter) return;
if (!waiter) {
return;
}
clearTranscriptWaiter(ctx, callId);
waiter.resolve(transcript);
}
+6 -2
View File
@@ -295,7 +295,9 @@ export class MediaStreamHandler {
private getTtsQueue(streamSid: string): TtsQueueEntry[] {
const existing = this.ttsQueues.get(streamSid);
if (existing) return existing;
if (existing) {
return existing;
}
const queue: TtsQueueEntry[] = [];
this.ttsQueues.set(streamSid, queue);
return queue;
@@ -339,7 +341,9 @@ export class MediaStreamHandler {
private clearTtsState(streamSid: string): void {
const queue = this.ttsQueues.get(streamSid);
if (queue) queue.length = 0;
if (queue) {
queue.length = 0;
}
this.ttsActiveControllers.get(streamSid)?.abort();
this.ttsActiveControllers.delete(streamSid);
this.ttsPlaying.delete(streamSid);
+9 -3
View File
@@ -37,11 +37,15 @@ export class MockProvider implements VoiceCallProvider {
if (Array.isArray(payload.events)) {
for (const evt of payload.events) {
const normalized = this.normalizeEvent(evt);
if (normalized) events.push(normalized);
if (normalized) {
events.push(normalized);
}
}
} else if (payload.event) {
const normalized = this.normalizeEvent(payload.event);
if (normalized) events.push(normalized);
if (normalized) {
events.push(normalized);
}
}
return { events, statusCode: 200 };
@@ -51,7 +55,9 @@ export class MockProvider implements VoiceCallProvider {
}
private normalizeEvent(evt: Partial<NormalizedEvent>): NormalizedEvent | null {
if (!evt.type || !evt.callId) return null;
if (!evt.type || !evt.callId) {
return null;
}
const base = {
id: evt.id || crypto.randomUUID(),
+18 -6
View File
@@ -123,7 +123,9 @@ export class PlivoProvider implements VoiceCallProvider {
if (flow === "xml-speak") {
const callId = this.getCallIdFromQuery(ctx);
const pending = callId ? this.pendingSpeakByCallId.get(callId) : undefined;
if (callId) this.pendingSpeakByCallId.delete(callId);
if (callId) {
this.pendingSpeakByCallId.delete(callId);
}
const xml = pending
? PlivoProvider.xmlSpeak(pending.text, pending.locale)
@@ -139,7 +141,9 @@ export class PlivoProvider implements VoiceCallProvider {
if (flow === "xml-listen") {
const callId = this.getCallIdFromQuery(ctx);
const pending = callId ? this.pendingListenByCallId.get(callId) : undefined;
if (callId) this.pendingListenByCallId.delete(callId);
if (callId) {
this.pendingListenByCallId.delete(callId);
}
const actionUrl = this.buildActionUrl(ctx, {
flow: "getinput",
@@ -393,7 +397,9 @@ export class PlivoProvider implements VoiceCallProvider {
private static normalizeNumber(numberOrSip: string): string {
const trimmed = numberOrSip.trim();
if (trimmed.toLowerCase().startsWith("sip:")) return trimmed;
if (trimmed.toLowerCase().startsWith("sip:")) {
return trimmed;
}
return trimmed.replace(/[^\d+]/g, "");
}
@@ -440,12 +446,16 @@ export class PlivoProvider implements VoiceCallProvider {
opts: { flow: string; callId?: string },
): string | null {
const base = PlivoProvider.baseWebhookUrlFromCtx(ctx);
if (!base) return null;
if (!base) {
return null;
}
const u = new URL(base);
u.searchParams.set("provider", "plivo");
u.searchParams.set("flow", opts.flow);
if (opts.callId) u.searchParams.set("callId", opts.callId);
if (opts.callId) {
u.searchParams.set("callId", opts.callId);
}
return u.toString();
}
@@ -478,7 +488,9 @@ export class PlivoProvider implements VoiceCallProvider {
for (const key of candidates) {
const value = params.get(key);
if (value && value.trim()) return value.trim();
if (value && value.trim()) {
return value.trim();
}
}
return null;
}
@@ -155,7 +155,9 @@ class OpenAIRealtimeSTTSession implements RealtimeSTTSession {
this.ws.on("error", (error) => {
console.error("[RealtimeSTT] WebSocket error:", error);
if (!this.connected) reject(error);
if (!this.connected) {
reject(error);
}
});
this.ws.on("close", (code, reason) => {
@@ -258,7 +260,9 @@ class OpenAIRealtimeSTTSession implements RealtimeSTTSession {
}
sendAudio(muLawData: Buffer): void {
if (!this.connected) return;
if (!this.connected) {
return;
}
this.sendEvent({
type: "input_audio_buffer.append",
audio: muLawData.toString("base64"),
@@ -205,10 +205,14 @@ function linearToMulaw(sample: number): number {
// Get sign bit
const sign = sample < 0 ? 0x80 : 0;
if (sample < 0) sample = -sample;
if (sample < 0) {
sample = -sample;
}
// Clip to prevent overflow
if (sample > CLIP) sample = CLIP;
if (sample > CLIP) {
sample = CLIP;
}
// Add bias and find segment
sample += BIAS;
+21 -7
View File
@@ -85,10 +85,14 @@ export class TwilioProvider implements VoiceCallProvider {
*/
private deleteStoredTwimlForProviderCall(providerCallId: string): void {
const webhookUrl = this.callWebhookUrls.get(providerCallId);
if (!webhookUrl) return;
if (!webhookUrl) {
return;
}
const callIdMatch = webhookUrl.match(/callId=([^&]+)/);
if (!callIdMatch) return;
if (!callIdMatch) {
return;
}
this.deleteStoredTwiml(callIdMatch[1]);
}
@@ -212,8 +216,12 @@ export class TwilioProvider implements VoiceCallProvider {
* Parse Twilio direction to normalized format.
*/
private static parseDirection(direction: string | null): "inbound" | "outbound" | undefined {
if (direction === "inbound") return "inbound";
if (direction === "outbound-api" || direction === "outbound-dial") return "outbound";
if (direction === "inbound") {
return "inbound";
}
if (direction === "outbound-api" || direction === "outbound-dial") {
return "outbound";
}
return undefined;
}
@@ -291,7 +299,9 @@ export class TwilioProvider implements VoiceCallProvider {
* When a call is answered, connects to media stream for bidirectional audio.
*/
private generateTwimlResponse(ctx?: WebhookContext): string {
if (!ctx) return TwilioProvider.EMPTY_TWIML;
if (!ctx) {
return TwilioProvider.EMPTY_TWIML;
}
const params = new URLSearchParams(ctx.rawBody);
const type = typeof ctx.query?.type === "string" ? ctx.query.type.trim() : undefined;
@@ -512,12 +522,16 @@ export class TwilioProvider implements VoiceCallProvider {
// Generate audio with core TTS (returns mu-law at 8kHz)
const muLawAudio = await ttsProvider.synthesizeForTelephony(text);
for (const chunk of chunkAudio(muLawAudio, CHUNK_SIZE)) {
if (signal.aborted) break;
if (signal.aborted) {
break;
}
handler.sendAudio(streamSid, chunk);
// Pace the audio to match real-time playback
await new Promise((resolve) => setTimeout(resolve, CHUNK_DELAY_MS));
if (signal.aborted) break;
if (signal.aborted) {
break;
}
}
if (!signal.aborted) {
+3 -1
View File
@@ -34,7 +34,9 @@ type Logger = {
};
function isLoopbackBind(bind: string | undefined): boolean {
if (!bind) return false;
if (!bind) {
return false;
}
return bind === "127.0.0.1" || bind === "::1" || bind === "localhost";
}
+12 -4
View File
@@ -8,9 +8,13 @@ function clamp16(value: number): number {
* Resample 16-bit PCM (little-endian mono) to 8kHz using linear interpolation.
*/
export function resamplePcmTo8k(input: Buffer, inputSampleRate: number): Buffer {
if (inputSampleRate === TELEPHONY_SAMPLE_RATE) return input;
if (inputSampleRate === TELEPHONY_SAMPLE_RATE) {
return input;
}
const inputSamples = Math.floor(input.length / 2);
if (inputSamples === 0) return Buffer.alloc(0);
if (inputSamples === 0) {
return Buffer.alloc(0);
}
const ratio = inputSampleRate / TELEPHONY_SAMPLE_RATE;
const outputSamples = Math.floor(inputSamples / ratio);
@@ -68,8 +72,12 @@ function linearToMulaw(sample: number): number {
const CLIP = 32635;
const sign = sample < 0 ? 0x80 : 0;
if (sample < 0) sample = -sample;
if (sample > CLIP) sample = CLIP;
if (sample < 0) {
sample = -sample;
}
if (sample > CLIP) {
sample = CLIP;
}
sample += BIAS;
let exponent = 7;
+19 -7
View File
@@ -45,16 +45,20 @@ export function createTelephonyTtsProvider(params: {
}
function applyTtsOverride(coreConfig: CoreConfig, override?: VoiceCallTtsConfig): CoreConfig {
if (!override) return coreConfig;
if (!override) {
return coreConfig;
}
const base = coreConfig.messages?.tts;
const merged = mergeTtsConfig(base, override);
if (!merged) return coreConfig;
if (!merged) {
return coreConfig;
}
return {
...coreConfig,
messages: {
...(coreConfig.messages ?? {}),
...coreConfig.messages,
tts: merged,
},
};
@@ -64,9 +68,15 @@ function mergeTtsConfig(
base?: VoiceCallTtsConfig,
override?: VoiceCallTtsConfig,
): VoiceCallTtsConfig | undefined {
if (!base && !override) return undefined;
if (!override) return base;
if (!base) return override;
if (!base && !override) {
return undefined;
}
if (!override) {
return base;
}
if (!base) {
return override;
}
return deepMerge(base, override);
}
@@ -76,7 +86,9 @@ function deepMerge<T>(base: T, override: T): T {
}
const result: Record<string, unknown> = { ...base };
for (const [key, value] of Object.entries(override)) {
if (value === undefined) continue;
if (value === undefined) {
continue;
}
const existing = (base as Record<string, unknown>)[key];
if (isPlainObject(existing) && isPlainObject(value)) {
result[key] = deepMerge(existing, value);
+3 -1
View File
@@ -3,7 +3,9 @@ import path from "node:path";
export function resolveUserPath(input: string): string {
const trimmed = input.trim();
if (!trimmed) return trimmed;
if (!trimmed) {
return trimmed;
}
if (trimmed.startsWith("~")) {
const expanded = trimmed.replace(/^~(?=$|[\\/])/, os.homedir());
return path.resolve(expanded);
+3 -1
View File
@@ -39,7 +39,9 @@ export const DEFAULT_POLLY_VOICE = "Polly.Joanna";
* @returns Polly voice name suitable for Twilio TwiML
*/
export function mapVoiceToPolly(voice: string | undefined): string {
if (!voice) return DEFAULT_POLLY_VOICE;
if (!voice) {
return DEFAULT_POLLY_VOICE;
}
// Already a Polly/Google voice - pass through
if (voice.startsWith("Polly.") || voice.startsWith("Google.")) {
@@ -29,7 +29,9 @@ function plivoV3Signature(params: {
const u = new URL(params.urlWithQuery);
const baseNoQuery = `${u.protocol}//${u.host}${u.pathname}`;
const queryPairs: Array<[string, string]> = [];
for (const [k, v] of u.searchParams.entries()) queryPairs.push([k, v]);
for (const [k, v] of u.searchParams.entries()) {
queryPairs.push([k, v]);
}
const queryMap = new Map<string, string[]>();
for (const [k, v] of queryPairs) {
@@ -37,8 +39,8 @@ function plivoV3Signature(params: {
}
const sortedQuery = Array.from(queryMap.keys())
.sort()
.flatMap((k) => [...(queryMap.get(k) ?? [])].sort().map((v) => `${k}=${v}`))
.toSorted()
.flatMap((k) => [...(queryMap.get(k) ?? [])].toSorted().map((v) => `${k}=${v}`))
.join("&");
const postParams = new URLSearchParams(params.postBody);
@@ -48,8 +50,8 @@ function plivoV3Signature(params: {
}
const sortedPost = Array.from(postMap.keys())
.sort()
.flatMap((k) => [...(postMap.get(k) ?? [])].sort().map((v) => `${k}${v}`))
.toSorted()
.flatMap((k) => [...(postMap.get(k) ?? [])].toSorted().map((v) => `${k}${v}`))
.join("");
const hasPost = sortedPost.length > 0;
@@ -71,7 +73,7 @@ function plivoV3Signature(params: {
function twilioSignature(params: { authToken: string; url: string; postBody: string }): string {
let dataToSign = params.url;
const sortedParams = Array.from(new URLSearchParams(params.postBody).entries()).sort((a, b) =>
const sortedParams = Array.from(new URLSearchParams(params.postBody).entries()).toSorted((a, b) =>
a[0].localeCompare(b[0]),
);
+20 -10
View File
@@ -24,7 +24,7 @@ export function validateTwilioSignature(
let dataToSign = url;
// Sort params alphabetically and append key+value
const sortedParams = Array.from(params.entries()).sort((a, b) =>
const sortedParams = Array.from(params.entries()).toSorted((a, b) =>
a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0,
);
@@ -129,9 +129,15 @@ function getHeader(
}
function isLoopbackAddress(address?: string): boolean {
if (!address) return false;
if (address === "127.0.0.1" || address === "::1") return true;
if (address.startsWith("::ffff:127.")) return true;
if (!address) {
return false;
}
if (address === "127.0.0.1" || address === "::1") {
return true;
}
if (address.startsWith("::ffff:127.")) {
return true;
}
return false;
}
@@ -272,7 +278,9 @@ type PlivoParamMap = Record<string, string[]>;
function toParamMapFromSearchParams(sp: URLSearchParams): PlivoParamMap {
const map: PlivoParamMap = {};
for (const [key, value] of sp.entries()) {
if (!map[key]) map[key] = [];
if (!map[key]) {
map[key] = [];
}
map[key].push(value);
}
return map;
@@ -280,8 +288,8 @@ function toParamMapFromSearchParams(sp: URLSearchParams): PlivoParamMap {
function sortedQueryString(params: PlivoParamMap): string {
const parts: string[] = [];
for (const key of Object.keys(params).sort()) {
const values = [...params[key]].sort();
for (const key of Object.keys(params).toSorted()) {
const values = [...params[key]].toSorted();
for (const value of values) {
parts.push(`${key}=${value}`);
}
@@ -291,8 +299,8 @@ function sortedQueryString(params: PlivoParamMap): string {
function sortedParamsString(params: PlivoParamMap): string {
const parts: string[] = [];
for (const key of Object.keys(params).sort()) {
const values = [...params[key]].sort();
for (const key of Object.keys(params).toSorted()) {
const values = [...params[key]].toSorted();
for (const value of values) {
parts.push(`${key}${value}`);
}
@@ -355,7 +363,9 @@ function validatePlivoV3Signature(params: {
.map((s) => normalizeSignatureBase64(s));
for (const sig of provided) {
if (timingSafeEqualString(expected, sig)) return true;
if (timingSafeEqualString(expected, sig)) {
return true;
}
}
return false;
}
+3 -1
View File
@@ -367,7 +367,9 @@ function runTailscaleCommand(
export async function getTailscaleSelfInfo(): Promise<TailscaleSelfInfo | null> {
const { code, stdout } = await runTailscaleCommand(["status", "--json"]);
if (code !== 0) return null;
if (code !== 0) {
return null;
}
try {
const status = JSON.parse(stdout);