Skip to content

Commit 5a33378

Browse files
authored
revert: iMessage group media attachment command (#86734)
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
1 parent 609d70d commit 5a33378

2 files changed

Lines changed: 16 additions & 228 deletions

File tree

extensions/imessage/src/send.test.ts

Lines changed: 16 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -60,45 +60,46 @@ describe("sendMessageIMessage receipts", () => {
6060
expect(result.receipt.sentAt).toBeGreaterThan(0);
6161
});
6262

63-
it("sends explicit chat media-only payloads through send-attachment auto transport", async () => {
63+
it("attaches a media receipt after attachment resolution", async () => {
6464
const client = createClient({ message_id: 12345 });
65-
const runCliJson = vi
66-
.fn()
67-
.mockResolvedValueOnce({ messageId: "p:0/media-guid", transferGuid: "transfer-1" });
6865

6966
const result = await sendMessageIMessage("chat_guid:chat-1", "", {
7067
config: IMESSAGE_TEST_CFG,
7168
client,
7269
mediaUrl: "/tmp/image.png",
7370
resolveAttachmentImpl: async () => ({ path: "/tmp/image.png", contentType: "image/png" }),
74-
runCliJson,
7571
});
7672

77-
expect(result.messageId).toBe("p:0/media-guid");
73+
expect(result.messageId).toBe("12345");
7874
expect(result.sentText).toBe("");
7975
expect(result.echoText).toBe("<media:image>");
80-
expect(result.receipt.primaryPlatformMessageId).toBe("p:0/media-guid");
81-
expect(result.receipt.platformMessageIds).toEqual(["p:0/media-guid"]);
82-
expect(client.request).not.toHaveBeenCalled();
83-
expect(runCliJson.mock.calls).toEqual([
84-
[["send-attachment", "--chat", "chat-1", "--file", "/tmp/image.png", "--transport", "auto"]],
85-
]);
76+
expect(result.receipt.primaryPlatformMessageId).toBe("12345");
77+
expect(result.receipt.platformMessageIds).toEqual(["12345"]);
78+
expect(client.request).toHaveBeenCalledWith(
79+
"send",
80+
expect.objectContaining({
81+
chat_guid: "chat-1",
82+
file: "/tmp/image.png",
83+
text: "",
84+
}),
85+
expect.any(Object),
86+
);
8687
expect(result.receipt.raw).toEqual([
8788
{
8889
channel: "imessage",
89-
messageId: "p:0/media-guid",
90+
messageId: "12345",
9091
conversationId: "chat-1",
9192
meta: { targetKind: "chat_guid" },
9293
},
9394
]);
9495
expect(result.receipt.parts).toEqual([
9596
{
9697
index: 0,
97-
platformMessageId: "p:0/media-guid",
98+
platformMessageId: "12345",
9899
kind: "media",
99100
raw: {
100101
channel: "imessage",
101-
messageId: "p:0/media-guid",
102+
messageId: "12345",
102103
conversationId: "chat-1",
103104
meta: { targetKind: "chat_guid" },
104105
},
@@ -107,63 +108,6 @@ describe("sendMessageIMessage receipts", () => {
107108
expect(result.receipt.sentAt).toBeGreaterThan(0);
108109
});
109110

110-
it("resolves chat_id media-only payloads before using send-attachment", async () => {
111-
const client = createClient({ message_id: 12345 });
112-
const runCliJson = vi
113-
.fn()
114-
.mockResolvedValueOnce({ guid: "any;+;group-guid" })
115-
.mockResolvedValueOnce({ messageId: "p:0/media-guid" });
116-
117-
const result = await sendMessageIMessage("chat_id:42", "", {
118-
config: IMESSAGE_TEST_CFG,
119-
client,
120-
mediaUrl: "/tmp/image.png",
121-
resolveAttachmentImpl: async () => ({ path: "/tmp/image.png", contentType: "image/png" }),
122-
runCliJson,
123-
});
124-
125-
expect(result.messageId).toBe("p:0/media-guid");
126-
expect(client.request).not.toHaveBeenCalled();
127-
expect(runCliJson.mock.calls).toEqual([
128-
[["group", "--chat-id", "42"]],
129-
[
130-
[
131-
"send-attachment",
132-
"--chat",
133-
"any;+;group-guid",
134-
"--file",
135-
"/tmp/image.png",
136-
"--transport",
137-
"auto",
138-
],
139-
],
140-
]);
141-
});
142-
143-
it("keeps DM handle media sends on the existing rpc send path", async () => {
144-
const client = createClient({ message_id: 12345 });
145-
const runCliJson = vi.fn();
146-
147-
await sendMessageIMessage("+15551234567", "", {
148-
config: IMESSAGE_TEST_CFG,
149-
client,
150-
mediaUrl: "/tmp/image.png",
151-
resolveAttachmentImpl: async () => ({ path: "/tmp/image.png", contentType: "image/png" }),
152-
runCliJson,
153-
});
154-
155-
expect(runCliJson).not.toHaveBeenCalled();
156-
expect(client.request).toHaveBeenCalledWith(
157-
"send",
158-
expect.objectContaining({
159-
to: "+15551234567",
160-
file: "/tmp/image.png",
161-
text: "",
162-
}),
163-
expect.any(Object),
164-
);
165-
});
166-
167111
it("preserves literal media placeholder text when no attachment is sent", async () => {
168112
const client = createClient({ guid: "p:0/imsg-text" });
169113

extensions/imessage/src/send.ts

Lines changed: 0 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { spawn } from "node:child_process";
21
import {
32
createMessageReceiptFromOutboundResults,
43
type MessageReceipt,
@@ -53,7 +52,6 @@ type IMessageSendOpts = {
5352
},
5453
) => Promise<{ path: string; contentType?: string }>;
5554
createClient?: (params: { cliPath: string; dbPath?: string }) => Promise<IMessageRpcClient>;
56-
runCliJson?: (args: readonly string[]) => Promise<Record<string, unknown>>;
5755
};
5856

5957
export type IMessageSendResult = {
@@ -212,110 +210,6 @@ function resolveOutboundEchoScope(params: {
212210
return `${params.accountId}:imessage:${params.target.to}`;
213211
}
214212

215-
function buildIMessageCliJsonArgs(args: readonly string[], dbPath?: string): string[] {
216-
const trimmedDbPath = dbPath?.trim();
217-
return [...args, ...(trimmedDbPath ? ["--db", trimmedDbPath] : []), "--json"];
218-
}
219-
220-
async function runIMessageCliJson(
221-
cliPath: string,
222-
dbPath: string | undefined,
223-
args: readonly string[],
224-
timeoutMs?: number,
225-
): Promise<Record<string, unknown>> {
226-
return await new Promise((resolve, reject) => {
227-
const child = spawn(cliPath, buildIMessageCliJsonArgs(args, dbPath), {
228-
stdio: ["ignore", "pipe", "pipe"],
229-
});
230-
let stdout = "";
231-
let stderr = "";
232-
let killEscalation: ReturnType<typeof setTimeout> | null = null;
233-
const timer =
234-
timeoutMs && timeoutMs > 0
235-
? setTimeout(() => {
236-
child.kill("SIGTERM");
237-
killEscalation = setTimeout(() => {
238-
try {
239-
child.kill("SIGKILL");
240-
} catch {
241-
// best-effort
242-
}
243-
}, 2000);
244-
reject(new Error(`iMessage action timed out after ${timeoutMs}ms`));
245-
}, timeoutMs)
246-
: null;
247-
child.stdout.setEncoding("utf8");
248-
child.stderr.setEncoding("utf8");
249-
child.stdout.on("data", (chunk) => {
250-
stdout += chunk;
251-
});
252-
child.stderr.on("data", (chunk) => {
253-
stderr += chunk;
254-
});
255-
child.on("error", (error) => {
256-
if (timer) {
257-
clearTimeout(timer);
258-
}
259-
if (killEscalation) {
260-
clearTimeout(killEscalation);
261-
}
262-
reject(error);
263-
});
264-
child.on("close", (code) => {
265-
if (timer) {
266-
clearTimeout(timer);
267-
}
268-
if (killEscalation) {
269-
clearTimeout(killEscalation);
270-
}
271-
const lines = stdout
272-
.split(/\r?\n/u)
273-
.map((line) => line.trim())
274-
.filter(Boolean);
275-
const last = lines.at(-1);
276-
let parsed: Record<string, unknown> | null = null;
277-
if (last) {
278-
try {
279-
const json = JSON.parse(last) as unknown;
280-
if (json && typeof json === "object" && !Array.isArray(json)) {
281-
parsed = json as Record<string, unknown>;
282-
}
283-
} catch {
284-
// handled below
285-
}
286-
}
287-
if (code === 0 && parsed) {
288-
resolve(parsed);
289-
return;
290-
}
291-
if (parsed && typeof parsed.error === "string" && parsed.error.trim()) {
292-
reject(new Error(parsed.error.trim()));
293-
return;
294-
}
295-
const detail = stderr.trim() || stdout.trim() || `imsg exited with code ${code}`;
296-
reject(new Error(detail));
297-
});
298-
});
299-
}
300-
301-
function stringValue(value: unknown): string | undefined {
302-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
303-
}
304-
305-
async function resolveAttachmentChatGuid(params: {
306-
target: ReturnType<typeof parseIMessageTarget>;
307-
runCliJson: (args: readonly string[]) => Promise<Record<string, unknown>>;
308-
}): Promise<string | null> {
309-
if (params.target.kind === "chat_guid") {
310-
return params.target.chatGuid;
311-
}
312-
if (params.target.kind !== "chat_id") {
313-
return null;
314-
}
315-
const result = await params.runCliJson(["group", "--chat-id", String(params.target.chatId)]);
316-
return stringValue(result.guid) ?? stringValue(result.chat_guid) ?? null;
317-
}
318-
319213
export async function sendMessageIMessage(
320214
to: string,
321215
text: string,
@@ -384,56 +278,6 @@ export async function sendMessageIMessage(
384278
}
385279
const echoText = resolveOutboundEchoText(message, filePath ? mediaContentType : undefined);
386280
const resolvedReplyToId = sanitizeReplyToId(opts.replyToId);
387-
const runCliJson =
388-
opts.runCliJson ??
389-
((args: readonly string[]) => runIMessageCliJson(cliPath, dbPath, args, opts.timeoutMs));
390-
391-
if (filePath && !message.trim() && !resolvedReplyToId) {
392-
const attachmentChatGuid = await resolveAttachmentChatGuid({ target, runCliJson });
393-
if (attachmentChatGuid) {
394-
const result = await runCliJson([
395-
"send-attachment",
396-
"--chat",
397-
attachmentChatGuid,
398-
"--file",
399-
filePath,
400-
"--transport",
401-
"auto",
402-
]);
403-
const resolvedId = resolveMessageId(result);
404-
const approvalBindingMessageId = resolveOutboundMessageGuid(result);
405-
const messageId = resolvedId ?? (result?.ok || result?.success ? "ok" : "unknown");
406-
const echoScope = resolveOutboundEchoScope({ accountId: account.accountId, target });
407-
if (echoScope) {
408-
rememberPersistedIMessageEcho({
409-
scope: echoScope,
410-
text: echoText,
411-
messageId: resolvedId ?? undefined,
412-
});
413-
}
414-
if (resolvedId) {
415-
rememberIMessageReplyCache({
416-
accountId: account.accountId,
417-
messageId: resolvedId,
418-
chatGuid: target.kind === "chat_guid" ? target.chatGuid : attachmentChatGuid,
419-
chatId: target.kind === "chat_id" ? target.chatId : undefined,
420-
timestamp: Date.now(),
421-
isFromMe: true,
422-
});
423-
}
424-
return {
425-
messageId,
426-
...(approvalBindingMessageId ? { guid: approvalBindingMessageId } : {}),
427-
sentText: message,
428-
...(echoText ? { echoText } : {}),
429-
receipt: createIMessageSendReceipt({
430-
messageId,
431-
target,
432-
kind: "media",
433-
}),
434-
};
435-
}
436-
}
437281
const params: Record<string, unknown> = {
438282
text: message,
439283
service: service || "auto",

0 commit comments

Comments
 (0)