mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 01:11:23 +00:00
chore: Enable "curly" rule to avoid single-statement if confusion/errors.
This commit is contained in:
@@ -13,19 +13,25 @@ export type ResolvedSignalAccount = {
|
||||
|
||||
function listConfiguredAccountIds(cfg: OpenClawConfig): string[] {
|
||||
const accounts = cfg.channels?.signal?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") return [];
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return [];
|
||||
}
|
||||
return Object.keys(accounts).filter(Boolean);
|
||||
}
|
||||
|
||||
export function listSignalAccountIds(cfg: OpenClawConfig): string[] {
|
||||
const ids = listConfiguredAccountIds(cfg);
|
||||
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
|
||||
if (ids.length === 0) {
|
||||
return [DEFAULT_ACCOUNT_ID];
|
||||
}
|
||||
return ids.toSorted((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
export function resolveDefaultSignalAccountId(cfg: OpenClawConfig): string {
|
||||
const ids = listSignalAccountIds(cfg);
|
||||
if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
|
||||
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
|
||||
return DEFAULT_ACCOUNT_ID;
|
||||
}
|
||||
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
@@ -34,7 +40,9 @@ function resolveAccountConfig(
|
||||
accountId: string,
|
||||
): SignalAccountConfig | undefined {
|
||||
const accounts = cfg.channels?.signal?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") return undefined;
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return accounts[accountId] as SignalAccountConfig | undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,9 @@ function normalizeBaseUrl(url: string): string {
|
||||
if (!trimmed) {
|
||||
throw new Error("Signal base URL is required");
|
||||
}
|
||||
if (/^https?:\/\//i.test(trimmed)) return trimmed.replace(/\/+$/, "");
|
||||
if (/^https?:\/\//i.test(trimmed)) {
|
||||
return trimmed.replace(/\/+$/, "");
|
||||
}
|
||||
return `http://${trimmed}`.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
@@ -117,7 +119,9 @@ export async function streamSignalEvents(params: {
|
||||
}): Promise<void> {
|
||||
const baseUrl = normalizeBaseUrl(params.baseUrl);
|
||||
const url = new URL(`${baseUrl}/api/v1/events`);
|
||||
if (params.account) url.searchParams.set("account", params.account);
|
||||
if (params.account) {
|
||||
url.searchParams.set("account", params.account);
|
||||
}
|
||||
|
||||
const fetchImpl = resolveFetch();
|
||||
if (!fetchImpl) {
|
||||
@@ -138,7 +142,9 @@ export async function streamSignalEvents(params: {
|
||||
let currentEvent: SignalSseEvent = {};
|
||||
|
||||
const flushEvent = () => {
|
||||
if (!currentEvent.data && !currentEvent.event && !currentEvent.id) return;
|
||||
if (!currentEvent.data && !currentEvent.event && !currentEvent.id) {
|
||||
return;
|
||||
}
|
||||
params.onEvent({
|
||||
event: currentEvent.event,
|
||||
data: currentEvent.data,
|
||||
@@ -149,13 +155,17 @@ export async function streamSignalEvents(params: {
|
||||
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
let lineEnd = buffer.indexOf("\n");
|
||||
while (lineEnd !== -1) {
|
||||
let line = buffer.slice(0, lineEnd);
|
||||
buffer = buffer.slice(lineEnd + 1);
|
||||
if (line.endsWith("\r")) line = line.slice(0, -1);
|
||||
if (line.endsWith("\r")) {
|
||||
line = line.slice(0, -1);
|
||||
}
|
||||
|
||||
if (line === "") {
|
||||
flushEvent();
|
||||
|
||||
@@ -20,11 +20,17 @@ export type SignalDaemonHandle = {
|
||||
|
||||
export function classifySignalCliLogLine(line: string): "log" | "error" | null {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) return null;
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
// signal-cli commonly writes all logs to stderr; treat severity explicitly.
|
||||
if (/\b(ERROR|WARN|WARNING)\b/.test(trimmed)) return "error";
|
||||
if (/\b(ERROR|WARN|WARNING)\b/.test(trimmed)) {
|
||||
return "error";
|
||||
}
|
||||
// Some signal-cli failures are not tagged with WARN/ERROR but should still be surfaced loudly.
|
||||
if (/\b(FAILED|SEVERE|EXCEPTION)\b/i.test(trimmed)) return "error";
|
||||
if (/\b(FAILED|SEVERE|EXCEPTION)\b/i.test(trimmed)) {
|
||||
return "error";
|
||||
}
|
||||
return "log";
|
||||
}
|
||||
|
||||
@@ -40,9 +46,15 @@ function buildDaemonArgs(opts: SignalDaemonOpts): string[] {
|
||||
if (opts.receiveMode) {
|
||||
args.push("--receive-mode", opts.receiveMode);
|
||||
}
|
||||
if (opts.ignoreAttachments) args.push("--ignore-attachments");
|
||||
if (opts.ignoreStories) args.push("--ignore-stories");
|
||||
if (opts.sendReadReceipts) args.push("--send-read-receipts");
|
||||
if (opts.ignoreAttachments) {
|
||||
args.push("--ignore-attachments");
|
||||
}
|
||||
if (opts.ignoreStories) {
|
||||
args.push("--ignore-stories");
|
||||
}
|
||||
if (opts.sendReadReceipts) {
|
||||
args.push("--send-read-receipts");
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
@@ -58,15 +70,21 @@ export function spawnSignalDaemon(opts: SignalDaemonOpts): SignalDaemonHandle {
|
||||
child.stdout?.on("data", (data) => {
|
||||
for (const line of data.toString().split(/\r?\n/)) {
|
||||
const kind = classifySignalCliLogLine(line);
|
||||
if (kind === "log") log(`signal-cli: ${line.trim()}`);
|
||||
else if (kind === "error") error(`signal-cli: ${line.trim()}`);
|
||||
if (kind === "log") {
|
||||
log(`signal-cli: ${line.trim()}`);
|
||||
} else if (kind === "error") {
|
||||
error(`signal-cli: ${line.trim()}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
child.stderr?.on("data", (data) => {
|
||||
for (const line of data.toString().split(/\r?\n/)) {
|
||||
const kind = classifySignalCliLogLine(line);
|
||||
if (kind === "log") log(`signal-cli: ${line.trim()}`);
|
||||
else if (kind === "error") error(`signal-cli: ${line.trim()}`);
|
||||
if (kind === "log") {
|
||||
log(`signal-cli: ${line.trim()}`);
|
||||
} else if (kind === "error") {
|
||||
error(`signal-cli: ${line.trim()}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
child.on("error", (err) => {
|
||||
|
||||
@@ -54,8 +54,12 @@ function mapStyle(style: MarkdownStyle): SignalTextStyle | null {
|
||||
|
||||
function mergeStyles(styles: SignalTextStyleRange[]): SignalTextStyleRange[] {
|
||||
const sorted = [...styles].toSorted((a, b) => {
|
||||
if (a.start !== b.start) return a.start - b.start;
|
||||
if (a.length !== b.length) return a.length - b.length;
|
||||
if (a.start !== b.start) {
|
||||
return a.start - b.start;
|
||||
}
|
||||
if (a.length !== b.length) {
|
||||
return a.length - b.length;
|
||||
}
|
||||
return a.style.localeCompare(b.style);
|
||||
});
|
||||
|
||||
@@ -80,7 +84,9 @@ function clampStyles(styles: SignalTextStyleRange[], maxLength: number): SignalT
|
||||
const start = Math.max(0, Math.min(style.start, maxLength));
|
||||
const end = Math.min(style.start + style.length, maxLength);
|
||||
const length = end - start;
|
||||
if (length > 0) clamped.push({ start, length, style: style.style });
|
||||
if (length > 0) {
|
||||
clamped.push({ start, length, style: style.style });
|
||||
}
|
||||
}
|
||||
return clamped;
|
||||
}
|
||||
@@ -89,7 +95,9 @@ function applyInsertionsToStyles(
|
||||
spans: SignalStyleSpan[],
|
||||
insertions: Insertion[],
|
||||
): SignalStyleSpan[] {
|
||||
if (insertions.length === 0) return spans;
|
||||
if (insertions.length === 0) {
|
||||
return spans;
|
||||
}
|
||||
const sortedInsertions = [...insertions].toSorted((a, b) => a.pos - b.pos);
|
||||
let updated = spans;
|
||||
|
||||
@@ -135,7 +143,9 @@ function applyInsertionsToStyles(
|
||||
|
||||
function renderSignalText(ir: MarkdownIR): SignalFormattedText {
|
||||
const text = ir.text ?? "";
|
||||
if (!text) return { text: "", styles: [] };
|
||||
if (!text) {
|
||||
return { text: "", styles: [] };
|
||||
}
|
||||
|
||||
const sortedLinks = [...ir.links].toSorted((a, b) => a.start - b.start);
|
||||
let out = "";
|
||||
@@ -143,7 +153,9 @@ function renderSignalText(ir: MarkdownIR): SignalFormattedText {
|
||||
const insertions: Insertion[] = [];
|
||||
|
||||
for (const link of sortedLinks) {
|
||||
if (link.start < cursor) continue;
|
||||
if (link.start < cursor) {
|
||||
continue;
|
||||
}
|
||||
out += text.slice(cursor, link.end);
|
||||
|
||||
const href = link.href.trim();
|
||||
@@ -170,7 +182,9 @@ function renderSignalText(ir: MarkdownIR): SignalFormattedText {
|
||||
const mappedStyles: SignalStyleSpan[] = ir.styles
|
||||
.map((span) => {
|
||||
const mapped = mapStyle(span.style);
|
||||
if (!mapped) return null;
|
||||
if (!mapped) {
|
||||
return null;
|
||||
}
|
||||
return { start: span.start, end: span.end, style: mapped };
|
||||
})
|
||||
.filter((span): span is SignalStyleSpan => span !== null);
|
||||
|
||||
@@ -17,7 +17,9 @@ function looksLikeUuid(value: string): boolean {
|
||||
return true;
|
||||
}
|
||||
const compact = value.replace(/-/g, "");
|
||||
if (!/^[0-9a-f]+$/i.test(compact)) return false;
|
||||
if (!/^[0-9a-f]+$/i.test(compact)) {
|
||||
return false;
|
||||
}
|
||||
return /[a-f]/i.test(compact);
|
||||
}
|
||||
|
||||
@@ -69,14 +71,20 @@ export function resolveSignalPeerId(sender: SignalSender): string {
|
||||
|
||||
function parseSignalAllowEntry(entry: string): SignalAllowEntry | null {
|
||||
const trimmed = entry.trim();
|
||||
if (!trimmed) return null;
|
||||
if (trimmed === "*") return { kind: "any" };
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
if (trimmed === "*") {
|
||||
return { kind: "any" };
|
||||
}
|
||||
|
||||
const stripped = stripSignalPrefix(trimmed);
|
||||
const lower = stripped.toLowerCase();
|
||||
if (lower.startsWith("uuid:")) {
|
||||
const raw = stripped.slice("uuid:".length).trim();
|
||||
if (!raw) return null;
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
return { kind: "uuid", raw };
|
||||
}
|
||||
|
||||
@@ -88,11 +96,15 @@ function parseSignalAllowEntry(entry: string): SignalAllowEntry | null {
|
||||
}
|
||||
|
||||
export function isSignalSenderAllowed(sender: SignalSender, allowFrom: string[]): boolean {
|
||||
if (allowFrom.length === 0) return false;
|
||||
if (allowFrom.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const parsed = allowFrom
|
||||
.map(parseSignalAllowEntry)
|
||||
.filter((entry): entry is SignalAllowEntry => entry !== null);
|
||||
if (parsed.some((entry) => entry.kind === "any")) return true;
|
||||
if (parsed.some((entry) => entry.kind === "any")) {
|
||||
return true;
|
||||
}
|
||||
return parsed.some((entry) => {
|
||||
if (entry.kind === "phone" && sender.kind === "phone") {
|
||||
return entry.e164 === sender.e164;
|
||||
@@ -110,8 +122,14 @@ export function isSignalGroupAllowed(params: {
|
||||
sender: SignalSender;
|
||||
}): boolean {
|
||||
const { groupPolicy, allowFrom, sender } = params;
|
||||
if (groupPolicy === "disabled") return false;
|
||||
if (groupPolicy === "open") return true;
|
||||
if (allowFrom.length === 0) return false;
|
||||
if (groupPolicy === "disabled") {
|
||||
return false;
|
||||
}
|
||||
if (groupPolicy === "open") {
|
||||
return true;
|
||||
}
|
||||
if (allowFrom.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return isSignalSenderAllowed(sender, allowFrom);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,9 @@ function resolveSignalReactionTargets(reaction: SignalReactionMessage): SignalRe
|
||||
function isSignalReactionMessage(
|
||||
reaction: SignalReactionMessage | null | undefined,
|
||||
): reaction is SignalReactionMessage {
|
||||
if (!reaction) return false;
|
||||
if (!reaction) {
|
||||
return false;
|
||||
}
|
||||
const emoji = reaction.emoji?.trim();
|
||||
const timestamp = reaction.targetSentTimestamp;
|
||||
const hasTarget = Boolean(reaction.targetAuthor?.trim() || reaction.targetAuthorUuid?.trim());
|
||||
@@ -111,10 +113,14 @@ function shouldEmitSignalReactionNotification(params: {
|
||||
}) {
|
||||
const { mode, account, targets, sender, allowlist } = params;
|
||||
const effectiveMode = mode ?? "own";
|
||||
if (effectiveMode === "off") return false;
|
||||
if (effectiveMode === "off") {
|
||||
return false;
|
||||
}
|
||||
if (effectiveMode === "own") {
|
||||
const accountId = account?.trim();
|
||||
if (!accountId || !targets || targets.length === 0) return false;
|
||||
if (!accountId || !targets || targets.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const normalizedAccount = normalizeE164(accountId);
|
||||
return targets.some((target) => {
|
||||
if (target.kind === "uuid") {
|
||||
@@ -124,7 +130,9 @@ function shouldEmitSignalReactionNotification(params: {
|
||||
});
|
||||
}
|
||||
if (effectiveMode === "allowlist") {
|
||||
if (!sender || !allowlist || allowlist.length === 0) return false;
|
||||
if (!sender || !allowlist || allowlist.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return isSignalSenderAllowed(sender, allowlist);
|
||||
}
|
||||
return true;
|
||||
@@ -160,7 +168,9 @@ async function waitForSignalDaemonReady(params: {
|
||||
runtime: params.runtime,
|
||||
check: async () => {
|
||||
const res = await signalCheck(params.baseUrl, 1000);
|
||||
if (res.ok) return { ok: true };
|
||||
if (res.ok) {
|
||||
return { ok: true };
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
error: res.error ?? (res.status ? `HTTP ${res.status}` : "unreachable"),
|
||||
@@ -178,7 +188,9 @@ async function fetchAttachment(params: {
|
||||
maxBytes: number;
|
||||
}): Promise<{ path: string; contentType?: string } | null> {
|
||||
const { attachment } = params;
|
||||
if (!attachment?.id) return null;
|
||||
if (!attachment?.id) {
|
||||
return null;
|
||||
}
|
||||
if (attachment.size && attachment.size > params.maxBytes) {
|
||||
throw new Error(
|
||||
`Signal attachment ${attachment.id} exceeds ${(params.maxBytes / (1024 * 1024)).toFixed(0)}MB limit`,
|
||||
@@ -187,15 +199,23 @@ async function fetchAttachment(params: {
|
||||
const rpcParams: Record<string, unknown> = {
|
||||
id: attachment.id,
|
||||
};
|
||||
if (params.account) rpcParams.account = params.account;
|
||||
if (params.groupId) rpcParams.groupId = params.groupId;
|
||||
else if (params.sender) rpcParams.recipient = params.sender;
|
||||
else return null;
|
||||
if (params.account) {
|
||||
rpcParams.account = params.account;
|
||||
}
|
||||
if (params.groupId) {
|
||||
rpcParams.groupId = params.groupId;
|
||||
} else if (params.sender) {
|
||||
rpcParams.recipient = params.sender;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await signalRpcRequest<{ data?: string }>("getAttachment", rpcParams, {
|
||||
baseUrl: params.baseUrl,
|
||||
});
|
||||
if (!result?.data) return null;
|
||||
if (!result?.data) {
|
||||
return null;
|
||||
}
|
||||
const buffer = Buffer.from(result.data, "base64");
|
||||
const saved = await saveMediaBuffer(
|
||||
buffer,
|
||||
@@ -222,7 +242,9 @@ async function deliverReplies(params: {
|
||||
for (const payload of replies) {
|
||||
const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
||||
const text = payload.text ?? "";
|
||||
if (!text && mediaList.length === 0) continue;
|
||||
if (!text && mediaList.length === 0) {
|
||||
continue;
|
||||
}
|
||||
if (mediaList.length === 0) {
|
||||
for (const chunk of chunkTextWithMode(text, textLimit, chunkMode)) {
|
||||
await sendMessageSignal(target, chunk, {
|
||||
@@ -367,7 +389,9 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
if (opts.abortSignal?.aborted) return;
|
||||
if (opts.abortSignal?.aborted) {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
} finally {
|
||||
opts.abortSignal?.removeEventListener("abort", onAbort);
|
||||
|
||||
@@ -176,7 +176,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
|
||||
const typingCallbacks = createTypingCallbacks({
|
||||
start: async () => {
|
||||
if (!ctxPayload.To) return;
|
||||
if (!ctxPayload.To) {
|
||||
return;
|
||||
}
|
||||
await sendTypingSignal(ctxPayload.To, {
|
||||
baseUrl: deps.baseUrl,
|
||||
account: deps.account,
|
||||
@@ -252,17 +254,25 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
debounceMs: inboundDebounceMs,
|
||||
buildKey: (entry) => {
|
||||
const conversationId = entry.isGroup ? (entry.groupId ?? "unknown") : entry.senderPeerId;
|
||||
if (!conversationId || !entry.senderPeerId) return null;
|
||||
if (!conversationId || !entry.senderPeerId) {
|
||||
return null;
|
||||
}
|
||||
return `signal:${deps.accountId}:${conversationId}:${entry.senderPeerId}`;
|
||||
},
|
||||
shouldDebounce: (entry) => {
|
||||
if (!entry.bodyText.trim()) return false;
|
||||
if (entry.mediaPath || entry.mediaType) return false;
|
||||
if (!entry.bodyText.trim()) {
|
||||
return false;
|
||||
}
|
||||
if (entry.mediaPath || entry.mediaType) {
|
||||
return false;
|
||||
}
|
||||
return !hasControlCommand(entry.bodyText, deps.cfg);
|
||||
},
|
||||
onFlush: async (entries) => {
|
||||
const last = entries.at(-1);
|
||||
if (!last) return;
|
||||
if (!last) {
|
||||
return;
|
||||
}
|
||||
if (entries.length === 1) {
|
||||
await handleSignalInboundMessage(last);
|
||||
return;
|
||||
@@ -271,7 +281,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
.map((entry) => entry.bodyText)
|
||||
.filter(Boolean)
|
||||
.join("\\n");
|
||||
if (!combinedText.trim()) return;
|
||||
if (!combinedText.trim()) {
|
||||
return;
|
||||
}
|
||||
await handleSignalInboundMessage({
|
||||
...last,
|
||||
bodyText: combinedText,
|
||||
@@ -285,7 +297,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
});
|
||||
|
||||
return async (event: { event?: string; data?: string }) => {
|
||||
if (event.event !== "receive" || !event.data) return;
|
||||
if (event.event !== "receive" || !event.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
let payload: SignalReceivePayload | null = null;
|
||||
try {
|
||||
@@ -298,13 +312,21 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
deps.runtime.error?.(`receive exception: ${payload.exception.message}`);
|
||||
}
|
||||
const envelope = payload?.envelope;
|
||||
if (!envelope) return;
|
||||
if (envelope.syncMessage) return;
|
||||
if (!envelope) {
|
||||
return;
|
||||
}
|
||||
if (envelope.syncMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sender = resolveSignalSender(envelope);
|
||||
if (!sender) return;
|
||||
if (!sender) {
|
||||
return;
|
||||
}
|
||||
if (deps.account && sender.kind === "phone") {
|
||||
if (sender.e164 === normalizeE164(deps.account)) return;
|
||||
if (sender.e164 === normalizeE164(deps.account)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const dataMessage = envelope.dataMessage ?? envelope.editMessage?.dataMessage;
|
||||
@@ -319,7 +341,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
Boolean(messageText || quoteText) || Boolean(!reaction && dataMessage?.attachments?.length);
|
||||
|
||||
if (reaction && !hasBodyContent) {
|
||||
if (reaction.isRemove) return; // Ignore reaction removals
|
||||
if (reaction.isRemove) {
|
||||
return;
|
||||
} // Ignore reaction removals
|
||||
const emojiLabel = reaction.emoji?.trim() || "emoji";
|
||||
const senderDisplay = formatSignalSenderDisplay(sender);
|
||||
const senderName = envelope.sourceName ?? senderDisplay;
|
||||
@@ -332,7 +356,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
sender,
|
||||
allowlist: deps.reactionAllowlist,
|
||||
});
|
||||
if (!shouldNotify) return;
|
||||
if (!shouldNotify) {
|
||||
return;
|
||||
}
|
||||
|
||||
const groupId = reaction.groupInfo?.groupId ?? undefined;
|
||||
const groupName = reaction.groupInfo?.groupName ?? undefined;
|
||||
@@ -373,13 +399,17 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
enqueueSystemEvent(text, { sessionKey: route.sessionKey, contextKey });
|
||||
return;
|
||||
}
|
||||
if (!dataMessage) return;
|
||||
if (!dataMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const senderDisplay = formatSignalSenderDisplay(sender);
|
||||
const senderRecipient = resolveSignalRecipient(sender);
|
||||
const senderPeerId = resolveSignalPeerId(sender);
|
||||
const senderAllowId = formatSignalSenderId(sender);
|
||||
if (!senderRecipient) return;
|
||||
if (!senderRecipient) {
|
||||
return;
|
||||
}
|
||||
const senderIdLine = formatSignalPairingIdLine(sender);
|
||||
const groupId = dataMessage.groupInfo?.groupId ?? undefined;
|
||||
const groupName = dataMessage.groupInfo?.groupName ?? undefined;
|
||||
@@ -391,7 +421,9 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
deps.dmPolicy === "open" ? true : isSignalSenderAllowed(sender, effectiveDmAllow);
|
||||
|
||||
if (!isGroup) {
|
||||
if (deps.dmPolicy === "disabled") return;
|
||||
if (deps.dmPolicy === "disabled") {
|
||||
return;
|
||||
}
|
||||
if (!dmAllowed) {
|
||||
if (deps.dmPolicy === "pairing") {
|
||||
const senderId = senderAllowId;
|
||||
@@ -490,11 +522,16 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
}
|
||||
|
||||
const kind = mediaKindFromMime(mediaType ?? undefined);
|
||||
if (kind) placeholder = `<media:${kind}>`;
|
||||
else if (dataMessage.attachments?.length) placeholder = "<media:attachment>";
|
||||
if (kind) {
|
||||
placeholder = `<media:${kind}>`;
|
||||
} else if (dataMessage.attachments?.length) {
|
||||
placeholder = "<media:attachment>";
|
||||
}
|
||||
|
||||
const bodyText = messageText || placeholder || dataMessage.quote?.text?.trim() || "";
|
||||
if (!bodyText) return;
|
||||
if (!bodyText) {
|
||||
return;
|
||||
}
|
||||
|
||||
const receiptTimestamp =
|
||||
typeof envelope.timestamp === "number"
|
||||
|
||||
@@ -9,10 +9,14 @@ export type SignalProbe = {
|
||||
};
|
||||
|
||||
function parseSignalVersion(value: unknown): string | null {
|
||||
if (typeof value === "string" && value.trim()) return value.trim();
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return value.trim();
|
||||
}
|
||||
if (typeof value === "object" && value !== null) {
|
||||
const version = (value as { version?: unknown }).version;
|
||||
if (typeof version === "string" && version.trim()) return version.trim();
|
||||
if (typeof version === "string" && version.trim()) {
|
||||
return version.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -23,13 +23,17 @@ export type SignalReactionResult = {
|
||||
|
||||
function normalizeSignalId(raw: string): string {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return "";
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
return trimmed.replace(/^signal:/i, "").trim();
|
||||
}
|
||||
|
||||
function normalizeSignalUuid(raw: string): string {
|
||||
const trimmed = normalizeSignalId(raw);
|
||||
if (!trimmed) return "";
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
if (trimmed.toLowerCase().startsWith("uuid:")) {
|
||||
return trimmed.slice("uuid:".length).trim();
|
||||
}
|
||||
@@ -44,9 +48,13 @@ function resolveTargetAuthorParams(params: {
|
||||
const candidates = [params.targetAuthor, params.targetAuthorUuid, params.fallback];
|
||||
for (const candidate of candidates) {
|
||||
const raw = candidate?.trim();
|
||||
if (!raw) continue;
|
||||
if (!raw) {
|
||||
continue;
|
||||
}
|
||||
const normalized = normalizeSignalUuid(raw);
|
||||
if (normalized) return { targetAuthor: normalized };
|
||||
if (normalized) {
|
||||
return { targetAuthor: normalized };
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -118,9 +126,15 @@ export async function sendReactionSignal(
|
||||
targetTimestamp,
|
||||
...targetAuthorParams,
|
||||
};
|
||||
if (normalizedRecipient) params.recipients = [normalizedRecipient];
|
||||
if (groupId) params.groupIds = [groupId];
|
||||
if (account) params.account = account;
|
||||
if (normalizedRecipient) {
|
||||
params.recipients = [normalizedRecipient];
|
||||
}
|
||||
if (groupId) {
|
||||
params.groupIds = [groupId];
|
||||
}
|
||||
if (account) {
|
||||
params.account = account;
|
||||
}
|
||||
|
||||
const result = await signalRpcRequest<{ timestamp?: number }>("sendReaction", params, {
|
||||
baseUrl,
|
||||
@@ -179,9 +193,15 @@ export async function removeReactionSignal(
|
||||
remove: true,
|
||||
...targetAuthorParams,
|
||||
};
|
||||
if (normalizedRecipient) params.recipients = [normalizedRecipient];
|
||||
if (groupId) params.groupIds = [groupId];
|
||||
if (account) params.account = account;
|
||||
if (normalizedRecipient) {
|
||||
params.recipients = [normalizedRecipient];
|
||||
}
|
||||
if (groupId) {
|
||||
params.groupIds = [groupId];
|
||||
}
|
||||
if (account) {
|
||||
params.account = account;
|
||||
}
|
||||
|
||||
const result = await signalRpcRequest<{ timestamp?: number }>("sendReaction", params, {
|
||||
baseUrl,
|
||||
|
||||
@@ -34,7 +34,9 @@ type SignalTarget =
|
||||
|
||||
function parseTarget(raw: string): SignalTarget {
|
||||
let value = raw.trim();
|
||||
if (!value) throw new Error("Signal recipient is required");
|
||||
if (!value) {
|
||||
throw new Error("Signal recipient is required");
|
||||
}
|
||||
const lower = value.toLowerCase();
|
||||
if (lower.startsWith("signal:")) {
|
||||
value = value.slice("signal:".length).trim();
|
||||
@@ -72,15 +74,21 @@ function buildTargetParams(
|
||||
allow: SignalTargetAllowlist,
|
||||
): SignalTargetParams | null {
|
||||
if (target.type === "recipient") {
|
||||
if (!allow.recipient) return null;
|
||||
if (!allow.recipient) {
|
||||
return null;
|
||||
}
|
||||
return { recipient: [target.recipient] };
|
||||
}
|
||||
if (target.type === "group") {
|
||||
if (!allow.group) return null;
|
||||
if (!allow.group) {
|
||||
return null;
|
||||
}
|
||||
return { groupId: target.groupId };
|
||||
}
|
||||
if (target.type === "username") {
|
||||
if (!allow.username) return null;
|
||||
if (!allow.username) {
|
||||
return null;
|
||||
}
|
||||
return { username: [target.username] };
|
||||
}
|
||||
return null;
|
||||
@@ -139,7 +147,9 @@ export async function sendMessageSignal(
|
||||
let textStyles: SignalTextStyleRange[] = [];
|
||||
const textMode = opts.textMode ?? "markdown";
|
||||
const maxBytes = (() => {
|
||||
if (typeof opts.maxBytes === "number") return opts.maxBytes;
|
||||
if (typeof opts.maxBytes === "number") {
|
||||
return opts.maxBytes;
|
||||
}
|
||||
if (typeof accountInfo.config.mediaMaxMb === "number") {
|
||||
return accountInfo.config.mediaMaxMb * 1024 * 1024;
|
||||
}
|
||||
@@ -186,7 +196,9 @@ export async function sendMessageSignal(
|
||||
(style) => `${style.start}:${style.length}:${style.style}`,
|
||||
);
|
||||
}
|
||||
if (account) params.account = account;
|
||||
if (account) {
|
||||
params.account = account;
|
||||
}
|
||||
if (attachments && attachments.length > 0) {
|
||||
params.attachments = attachments;
|
||||
}
|
||||
@@ -221,10 +233,16 @@ export async function sendTypingSignal(
|
||||
recipient: true,
|
||||
group: true,
|
||||
});
|
||||
if (!targetParams) return false;
|
||||
if (!targetParams) {
|
||||
return false;
|
||||
}
|
||||
const params: Record<string, unknown> = { ...targetParams };
|
||||
if (account) params.account = account;
|
||||
if (opts.stop) params.stop = true;
|
||||
if (account) {
|
||||
params.account = account;
|
||||
}
|
||||
if (opts.stop) {
|
||||
params.stop = true;
|
||||
}
|
||||
await signalRpcRequest("sendTyping", params, {
|
||||
baseUrl,
|
||||
timeoutMs: opts.timeoutMs,
|
||||
@@ -237,18 +255,24 @@ export async function sendReadReceiptSignal(
|
||||
targetTimestamp: number,
|
||||
opts: SignalRpcOpts & { type?: SignalReceiptType } = {},
|
||||
): Promise<boolean> {
|
||||
if (!Number.isFinite(targetTimestamp) || targetTimestamp <= 0) return false;
|
||||
if (!Number.isFinite(targetTimestamp) || targetTimestamp <= 0) {
|
||||
return false;
|
||||
}
|
||||
const { baseUrl, account } = resolveSignalRpcContext(opts);
|
||||
const targetParams = buildTargetParams(parseTarget(to), {
|
||||
recipient: true,
|
||||
});
|
||||
if (!targetParams) return false;
|
||||
if (!targetParams) {
|
||||
return false;
|
||||
}
|
||||
const params: Record<string, unknown> = {
|
||||
...targetParams,
|
||||
targetTimestamp,
|
||||
type: opts.type ?? "read",
|
||||
};
|
||||
if (account) params.account = account;
|
||||
if (account) {
|
||||
params.account = account;
|
||||
}
|
||||
await signalRpcRequest("sendReceipt", params, {
|
||||
baseUrl,
|
||||
timeoutMs: opts.timeoutMs,
|
||||
|
||||
@@ -35,7 +35,9 @@ export async function runSignalSseLoop({
|
||||
let reconnectAttempts = 0;
|
||||
|
||||
const logReconnectVerbose = (message: string) => {
|
||||
if (!shouldLogVerbose()) return;
|
||||
if (!shouldLogVerbose()) {
|
||||
return;
|
||||
}
|
||||
logVerbose(message);
|
||||
};
|
||||
|
||||
@@ -50,13 +52,17 @@ export async function runSignalSseLoop({
|
||||
onEvent(event);
|
||||
},
|
||||
});
|
||||
if (abortSignal?.aborted) return;
|
||||
if (abortSignal?.aborted) {
|
||||
return;
|
||||
}
|
||||
reconnectAttempts += 1;
|
||||
const delayMs = computeBackoff(reconnectPolicy, reconnectAttempts);
|
||||
logReconnectVerbose(`Signal SSE stream ended, reconnecting in ${delayMs / 1000}s...`);
|
||||
await sleepWithAbort(delayMs, abortSignal);
|
||||
} catch (err) {
|
||||
if (abortSignal?.aborted) return;
|
||||
if (abortSignal?.aborted) {
|
||||
return;
|
||||
}
|
||||
runtime.error?.(`Signal SSE stream error: ${String(err)}`);
|
||||
reconnectAttempts += 1;
|
||||
const delayMs = computeBackoff(reconnectPolicy, reconnectAttempts);
|
||||
@@ -64,7 +70,9 @@ export async function runSignalSseLoop({
|
||||
try {
|
||||
await sleepWithAbort(delayMs, abortSignal);
|
||||
} catch (sleepErr) {
|
||||
if (abortSignal?.aborted) return;
|
||||
if (abortSignal?.aborted) {
|
||||
return;
|
||||
}
|
||||
throw sleepErr;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user