refactor(outbound): reuse signal uuid detection and payload types

This commit is contained in:
Peter Steinberger
2026-02-22 17:54:24 +00:00
parent dacb3d1aa2
commit b3c78e5e05
4 changed files with 78 additions and 59 deletions

View File

@@ -25,9 +25,7 @@ type DeliveryMirrorPayload = {
mediaUrls?: string[];
};
export interface QueuedDelivery {
id: string;
enqueuedAt: number;
type QueuedDeliveryPayload = {
channel: Exclude<OutboundChannel, "none">;
to: string;
accountId?: string;
@@ -43,6 +41,11 @@ export interface QueuedDelivery {
gifPlayback?: boolean;
silent?: boolean;
mirror?: DeliveryMirrorPayload;
};
export interface QueuedDelivery extends QueuedDeliveryPayload {
id: string;
enqueuedAt: number;
retryCount: number;
lastError?: string;
}
@@ -65,18 +68,7 @@ export async function ensureQueueDir(stateDir?: string): Promise<string> {
}
/** Persist a delivery entry to disk before attempting send. Returns the entry ID. */
type QueuedDeliveryParams = {
channel: Exclude<OutboundChannel, "none">;
to: string;
accountId?: string;
payloads: ReplyPayload[];
threadId?: string | number | null;
replyToId?: string | null;
bestEffort?: boolean;
gifPlayback?: boolean;
silent?: boolean;
mirror?: DeliveryMirrorPayload;
};
type QueuedDeliveryParams = QueuedDeliveryPayload;
export async function enqueueDelivery(
params: QueuedDeliveryParams,

View File

@@ -9,6 +9,7 @@ import { parseIMessageTarget, normalizeIMessageHandle } from "../../imessage/tar
import { buildAgentSessionKey, type RoutePeer } from "../../routing/resolve-route.js";
import { resolveThreadSessionKeys } from "../../routing/session-key.js";
import {
looksLikeUuid,
resolveSignalPeerId,
resolveSignalRecipient,
resolveSignalSender,
@@ -44,22 +45,9 @@ export type ResolveOutboundSessionRouteParams = {
threadId?: string | number | null;
};
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const UUID_COMPACT_RE = /^[0-9a-f]{32}$/i;
// Cache Slack channel type lookups to avoid repeated API calls.
const SLACK_CHANNEL_TYPE_CACHE = new Map<string, "channel" | "group" | "dm" | "unknown">();
function looksLikeUuid(value: string): boolean {
if (UUID_RE.test(value) || UUID_COMPACT_RE.test(value)) {
return true;
}
const compact = value.replace(/-/g, "");
if (!/^[0-9a-f]+$/i.test(compact)) {
return false;
}
return /[a-f]/i.test(compact);
}
function normalizeThreadId(value?: string | number | null): string | undefined {
if (value == null) {
return undefined;
@@ -669,9 +657,15 @@ function resolveNextcloudTalkSession(
function resolveZaloSession(
params: ResolveOutboundSessionRouteParams,
): OutboundSessionRoute | null {
const trimmed = stripProviderPrefix(params.target, "zalo")
.replace(/^(zl):/i, "")
.trim();
return resolveZaloLikeSession(params, "zalo", /^(zl):/i);
}
function resolveZaloLikeSession(
params: ResolveOutboundSessionRouteParams,
channel: "zalo" | "zalouser",
aliasPrefix: RegExp,
): OutboundSessionRoute | null {
const trimmed = stripProviderPrefix(params.target, channel).replace(aliasPrefix, "").trim();
if (!trimmed) {
return null;
}
@@ -681,7 +675,7 @@ function resolveZaloSession(
const baseSessionKey = buildBaseSessionKey({
cfg: params.cfg,
agentId: params.agentId,
channel: "zalo",
channel,
accountId: params.accountId,
peer,
});
@@ -690,39 +684,16 @@ function resolveZaloSession(
baseSessionKey,
peer,
chatType: isGroup ? "group" : "direct",
from: isGroup ? `zalo:group:${peerId}` : `zalo:${peerId}`,
to: `zalo:${peerId}`,
from: isGroup ? `${channel}:group:${peerId}` : `${channel}:${peerId}`,
to: `${channel}:${peerId}`,
};
}
function resolveZalouserSession(
params: ResolveOutboundSessionRouteParams,
): OutboundSessionRoute | null {
const trimmed = stripProviderPrefix(params.target, "zalouser")
.replace(/^(zlu):/i, "")
.trim();
if (!trimmed) {
return null;
}
const isGroup = trimmed.toLowerCase().startsWith("group:");
const peerId = stripKindPrefix(trimmed);
// Keep DM vs group aligned with inbound sessions for Zalo Personal.
const peer: RoutePeer = { kind: isGroup ? "group" : "direct", id: peerId };
const baseSessionKey = buildBaseSessionKey({
cfg: params.cfg,
agentId: params.agentId,
channel: "zalouser",
accountId: params.accountId,
peer,
});
return {
sessionKey: baseSessionKey,
baseSessionKey,
peer,
chatType: isGroup ? "group" : "direct",
from: isGroup ? `zalouser:group:${peerId}` : `zalouser:${peerId}`,
to: `zalouser:${peerId}`,
};
return resolveZaloLikeSession(params, "zalouser", /^(zlu):/i);
}
function resolveNostrSession(

View File

@@ -0,0 +1,56 @@
import { describe, expect, it } from "vitest";
import {
looksLikeUuid,
resolveSignalPeerId,
resolveSignalRecipient,
resolveSignalSender,
} from "./identity.js";
describe("looksLikeUuid", () => {
it("accepts hyphenated UUIDs", () => {
expect(looksLikeUuid("123e4567-e89b-12d3-a456-426614174000")).toBe(true);
});
it("accepts compact UUIDs", () => {
expect(looksLikeUuid("123e4567e89b12d3a456426614174000")).toBe(true);
});
it("accepts uuid-like hex values with letters", () => {
expect(looksLikeUuid("abcd-1234")).toBe(true);
});
it("rejects numeric ids and phone-like values", () => {
expect(looksLikeUuid("1234567890")).toBe(false);
expect(looksLikeUuid("+15555551212")).toBe(false);
});
});
describe("signal sender identity", () => {
it("prefers sourceNumber over sourceUuid", () => {
const sender = resolveSignalSender({
sourceNumber: " +15550001111 ",
sourceUuid: "123e4567-e89b-12d3-a456-426614174000",
});
expect(sender).toEqual({
kind: "phone",
raw: "+15550001111",
e164: "+15550001111",
});
});
it("uses sourceUuid when sourceNumber is missing", () => {
const sender = resolveSignalSender({
sourceUuid: "123e4567-e89b-12d3-a456-426614174000",
});
expect(sender).toEqual({
kind: "uuid",
raw: "123e4567-e89b-12d3-a456-426614174000",
});
});
it("maps uuid senders to recipient and peer ids", () => {
const sender = { kind: "uuid", raw: "123e4567-e89b-12d3-a456-426614174000" } as const;
expect(resolveSignalRecipient(sender)).toBe("123e4567-e89b-12d3-a456-426614174000");
expect(resolveSignalPeerId(sender)).toBe("uuid:123e4567-e89b-12d3-a456-426614174000");
});
});

View File

@@ -12,7 +12,7 @@ type SignalAllowEntry =
const UUID_HYPHENATED_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const UUID_COMPACT_RE = /^[0-9a-f]{32}$/i;
function looksLikeUuid(value: string): boolean {
export function looksLikeUuid(value: string): boolean {
if (UUID_HYPHENATED_RE.test(value) || UUID_COMPACT_RE.test(value)) {
return true;
}