spectrum-ts 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import {
2
2
  asGroup,
3
3
  asRichlink,
4
4
  groupSchema
5
- } from "../../chunk-K3CTEGCZ.js";
5
+ } from "../../chunk-66GJ45ZZ.js";
6
6
  import {
7
7
  asPoll,
8
8
  asPollOption,
@@ -23,7 +23,7 @@ import {
23
23
  text,
24
24
  textSchema,
25
25
  toVCard
26
- } from "../../chunk-UQPIWAHH.js";
26
+ } from "../../chunk-LH4YEBG3.js";
27
27
 
28
28
  // src/providers/imessage/index.ts
29
29
  import {
@@ -77,18 +77,36 @@ function effect(input, messageEffect) {
77
77
  }
78
78
 
79
79
  // src/providers/imessage/auth.ts
80
- import {
81
- createClient
82
- } from "@photon-ai/advanced-imessage";
80
+ import { createClient } from "@photon-ai/advanced-imessage";
83
81
  var RENEWAL_RATIO = 0.8;
84
82
  var EXPIRY_BUFFER_MS = 3e4;
85
83
  var RETRY_DELAY_MS = 3e4;
86
84
  var cloudAuthState = /* @__PURE__ */ new WeakMap();
85
+ var requirePhone = (data, instanceId) => {
86
+ const phone = data.numbers?.[instanceId];
87
+ if (!phone) {
88
+ throw new Error(`iMessage instance ${instanceId} has no phone assigned`);
89
+ }
90
+ return phone;
91
+ };
87
92
  async function createCloudClients(projectId, projectSecret) {
88
93
  let tokenData = await cloud.issueImessageTokens(projectId, projectSecret);
89
94
  let tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
90
95
  let disposed = false;
91
96
  let renewalTimer;
97
+ if (tokenData.type === "shared") {
98
+ throw UnsupportedError.action(
99
+ "multi-phone",
100
+ "iMessage shared mode",
101
+ "use dedicated-token cloud mode"
102
+ );
103
+ }
104
+ const records = [];
105
+ const syncPhones = (data) => {
106
+ for (const { entry, instanceId } of records) {
107
+ entry.phone = requirePhone(data, instanceId);
108
+ }
109
+ };
92
110
  const scheduleRenewal = () => {
93
111
  if (disposed) {
94
112
  return;
@@ -98,7 +116,15 @@ async function createCloudClients(projectId, projectSecret) {
98
116
  renewalTimer = setTimeout(async () => {
99
117
  try {
100
118
  tokenData = await cloud.issueImessageTokens(projectId, projectSecret);
119
+ if (tokenData.type === "shared") {
120
+ throw UnsupportedError.action(
121
+ "multi-phone",
122
+ "iMessage shared mode",
123
+ "use dedicated-token cloud mode"
124
+ );
125
+ }
101
126
  tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
127
+ syncPhones(tokenData);
102
128
  scheduleRenewal();
103
129
  } catch {
104
130
  renewalTimer = setTimeout(() => scheduleRenewal(), RETRY_DELAY_MS);
@@ -113,25 +139,22 @@ async function createCloudClients(projectId, projectSecret) {
113
139
  return;
114
140
  }
115
141
  tokenData = await cloud.issueImessageTokens(projectId, projectSecret);
142
+ if (tokenData.type === "shared") {
143
+ throw UnsupportedError.action(
144
+ "multi-phone",
145
+ "iMessage shared mode",
146
+ "use dedicated-token cloud mode"
147
+ );
148
+ }
116
149
  tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
150
+ syncPhones(tokenData);
117
151
  scheduleRenewal();
118
152
  };
119
- const buildClients = () => {
120
- if (tokenData.type === "shared") {
121
- const address = process.env.SPECTRUM_IMESSAGE_ADDRESS ?? "imessage.spectrum.photon.codes:443";
122
- return [
123
- createClient({
124
- address,
125
- tls: true,
126
- token: async () => {
127
- await refreshIfNeeded();
128
- return tokenData.token;
129
- }
130
- })
131
- ];
132
- }
133
- return Object.entries(tokenData.auth).map(
134
- ([instanceId, token]) => createClient({
153
+ const dedicated = tokenData;
154
+ for (const [instanceId, token] of Object.entries(dedicated.auth)) {
155
+ const entry = {
156
+ phone: requirePhone(dedicated, instanceId),
157
+ client: createClient({
135
158
  address: `${instanceId}.imsg.photon.codes:443`,
136
159
  tls: true,
137
160
  token: async () => {
@@ -140,10 +163,11 @@ async function createCloudClients(projectId, projectSecret) {
140
163
  return data.auth[instanceId] ?? token;
141
164
  }
142
165
  })
143
- );
144
- };
145
- const clients = buildClients();
146
- cloudAuthState.set(clients, {
166
+ };
167
+ records.push({ entry, instanceId });
168
+ }
169
+ const entries = records.map((r) => r.entry);
170
+ cloudAuthState.set(entries, {
147
171
  dispose: () => {
148
172
  disposed = true;
149
173
  if (renewalTimer !== void 0) {
@@ -152,7 +176,7 @@ async function createCloudClients(projectId, projectSecret) {
152
176
  }
153
177
  }
154
178
  });
155
- return clients;
179
+ return entries;
156
180
  }
157
181
  async function disposeCloudAuth(clients) {
158
182
  const auth = cloudAuthState.get(clients);
@@ -230,7 +254,12 @@ var toMessages = async (message) => {
230
254
  }
231
255
  const base = {
232
256
  sender: { id: message.participant ?? "" },
233
- space: { id: chatId, type: chatKind === "group" ? "group" : "dm" },
257
+ // Local mode has no concept of "which-of-my-phones"; phone is empty.
258
+ space: {
259
+ id: chatId,
260
+ type: chatKind === "group" ? "group" : "dm",
261
+ phone: ""
262
+ },
234
263
  timestamp: message.createdAt
235
264
  };
236
265
  if (message.attachments.length > 0) {
@@ -337,17 +366,6 @@ var messages2 = (client) => messages(client);
337
366
  var send2 = async (client, spaceId, content) => send(client, spaceId, content);
338
367
  var getMessage2 = async (client, id) => getMessage(client, id);
339
368
 
340
- // src/providers/imessage/remote/client.ts
341
- var REMOTE_CLIENT_MISSING = "No remote iMessage client available";
342
- var firstRemoteClient = (clients) => clients[0];
343
- var primaryRemoteClient = (clients) => {
344
- const remote = firstRemoteClient(clients);
345
- if (!remote) {
346
- throw new Error(REMOTE_CLIENT_MISSING);
347
- }
348
- return remote;
349
- };
350
-
351
369
  // src/providers/imessage/remote/inbound.ts
352
370
  import {
353
371
  messageGuid,
@@ -520,13 +538,14 @@ var isIMessageMessage = (value) => {
520
538
  return typeof record.id === "string" && record.id.length > 0 && typeof record.content === "object" && record.content !== null && typeof record.sender === "object" && record.sender !== null && typeof record.space === "object" && record.space !== null;
521
539
  };
522
540
  var asProviderGroup = (items) => groupSchema.parse({ type: "group", items });
523
- var buildMessageBase = (message, chatGuidHint, timestamp) => {
541
+ var buildMessageBase = (message, chatGuidHint, timestamp, phone) => {
524
542
  const chat = resolveChatGuid(message, chatGuidHint);
525
543
  return {
526
544
  sender: { id: resolveSenderId(message) },
527
545
  space: {
528
546
  id: chat,
529
- type: chat.includes(";+;") ? "group" : "dm"
547
+ type: chat.includes(";+;") ? "group" : "dm",
548
+ phone
530
549
  },
531
550
  timestamp
532
551
  };
@@ -581,10 +600,10 @@ var toRichlinkMessage = (message, base, id) => {
581
600
  };
582
601
  }
583
602
  };
584
- var rebuildFromAppleMessage = async (client, message, chatGuidHint) => {
603
+ var rebuildFromAppleMessage = async (client, message, phone, chatGuidHint) => {
585
604
  const messageGuidStr = message.guid;
586
605
  const timestamp = message.dateCreated ?? /* @__PURE__ */ new Date();
587
- const base = buildMessageBase(message, chatGuidHint, timestamp);
606
+ const base = buildMessageBase(message, chatGuidHint, timestamp, phone);
588
607
  if (message.attachments.length === 1) {
589
608
  const info = message.attachments[0];
590
609
  if (!info) {
@@ -636,8 +655,13 @@ var cacheMessage = (cache, message) => {
636
655
  }
637
656
  }
638
657
  };
639
- var toInboundMessages = async (client, cache, event) => {
640
- const base = buildMessageBase(event.message, event.chatGuid, event.timestamp);
658
+ var toInboundMessages = async (client, cache, event, phone) => {
659
+ const base = buildMessageBase(
660
+ event.message,
661
+ event.chatGuid,
662
+ event.timestamp,
663
+ phone
664
+ );
641
665
  const messageGuidStr = event.message.guid;
642
666
  if (getBalloonBundleId(event.message) === URL_BALLOON_BUNDLE_ID) {
643
667
  const msg2 = toRichlinkMessage(event.message, base, messageGuidStr);
@@ -694,7 +718,7 @@ var toInboundMessages = async (client, cache, event) => {
694
718
  cacheMessage(cache, msg);
695
719
  return [msg];
696
720
  };
697
- var getMessage3 = async (remote, spaceId, msgId) => {
721
+ var getMessage3 = async (remote, spaceId, msgId, phone) => {
698
722
  const cache = getMessageCache(remote);
699
723
  const cached = cache.get(msgId);
700
724
  if (cached) {
@@ -706,7 +730,12 @@ var getMessage3 = async (remote, spaceId, msgId) => {
706
730
  const fetched = await remote.messages.get(
707
731
  messageGuid(childRef.parentGuid)
708
732
  );
709
- const parent = await rebuildFromAppleMessage(remote, fetched, spaceId);
733
+ const parent = await rebuildFromAppleMessage(
734
+ remote,
735
+ fetched,
736
+ phone,
737
+ spaceId
738
+ );
710
739
  cacheMessage(cache, parent);
711
740
  if (parent.content.type !== "group") {
712
741
  return;
@@ -722,7 +751,12 @@ var getMessage3 = async (remote, spaceId, msgId) => {
722
751
  }
723
752
  try {
724
753
  const fetched = await remote.messages.get(messageGuid(msgId));
725
- const rebuilt = await rebuildFromAppleMessage(remote, fetched, spaceId);
754
+ const rebuilt = await rebuildFromAppleMessage(
755
+ remote,
756
+ fetched,
757
+ phone,
758
+ spaceId
759
+ );
726
760
  cacheMessage(cache, rebuilt);
727
761
  return rebuilt;
728
762
  } catch (err) {
@@ -785,12 +819,12 @@ var asProviderReaction = (emoji, target) => reactionSchema.parse({
785
819
  target,
786
820
  type: "reaction"
787
821
  });
788
- var resolveReactionTarget = async (client, cache, strippedGuid, partIndex) => {
822
+ var resolveReactionTarget = async (client, cache, strippedGuid, partIndex, phone) => {
789
823
  let candidate = cache.get(strippedGuid);
790
824
  if (!candidate) {
791
825
  try {
792
826
  const fetched = await client.messages.get(messageGuid2(strippedGuid));
793
- candidate = await rebuildFromAppleMessage(client, fetched);
827
+ candidate = await rebuildFromAppleMessage(client, fetched, phone);
794
828
  cacheMessage(cache, candidate);
795
829
  } catch {
796
830
  return;
@@ -806,7 +840,7 @@ var resolveReactionTarget = async (client, cache, strippedGuid, partIndex) => {
806
840
  }
807
841
  return candidate;
808
842
  };
809
- var toReactionMessages = async (client, cache, event, target) => {
843
+ var toReactionMessages = async (client, cache, event, target, phone) => {
810
844
  const type = getAssociatedMessageType(event.message);
811
845
  if (type && isTapbackRemoval(type)) {
812
846
  return [];
@@ -823,7 +857,8 @@ var toReactionMessages = async (client, cache, event, target) => {
823
857
  client,
824
858
  cache,
825
859
  strippedGuid,
826
- partIndex
860
+ partIndex,
861
+ phone
827
862
  );
828
863
  if (!resolved) {
829
864
  return [];
@@ -832,7 +867,12 @@ var toReactionMessages = async (client, cache, event, target) => {
832
867
  if (typeof messageId !== "string" || messageId.length === 0) {
833
868
  return [];
834
869
  }
835
- const base = buildMessageBase(event.message, event.chatGuid, event.timestamp);
870
+ const base = buildMessageBase(
871
+ event.message,
872
+ event.chatGuid,
873
+ event.timestamp,
874
+ phone
875
+ );
836
876
  return [
837
877
  {
838
878
  ...base,
@@ -1545,7 +1585,8 @@ var buildPollOptionMessage = (input) => {
1545
1585
  sender: { id: input.senderAddress },
1546
1586
  space: {
1547
1587
  id: input.chatGuid,
1548
- type: input.chatGuid.includes(";+;") ? "group" : "dm"
1588
+ type: input.chatGuid.includes(";+;") ? "group" : "dm",
1589
+ phone: input.phone
1549
1590
  },
1550
1591
  timestamp: input.event.at,
1551
1592
  content: asPollOption({
@@ -1563,6 +1604,7 @@ var buildPollOptionMessages = (input) => {
1563
1604
  chatGuid: input.chatGuid,
1564
1605
  event: input.event,
1565
1606
  optionId: delta.optionId,
1607
+ phone: input.phone,
1566
1608
  selected: delta.selected,
1567
1609
  senderAddress: input.senderAddress
1568
1610
  });
@@ -1587,7 +1629,7 @@ var refreshPollMetadata = async (client, pollCache, event, fallbackOptionIds) =>
1587
1629
  poll: refreshed
1588
1630
  };
1589
1631
  };
1590
- var toPollVoteMessages = async (client, pollCache, event) => {
1632
+ var toPollVoteMessages = async (client, pollCache, event, phone) => {
1591
1633
  const senderAddress = event.actor.address;
1592
1634
  if (!senderAddress) {
1593
1635
  return [];
@@ -1630,6 +1672,7 @@ var toPollVoteMessages = async (client, pollCache, event) => {
1630
1672
  chatGuid: chatGuidStr,
1631
1673
  deltas,
1632
1674
  event,
1675
+ phone,
1633
1676
  senderAddress
1634
1677
  });
1635
1678
  pollCache.commitActorSelection(
@@ -1640,7 +1683,7 @@ var toPollVoteMessages = async (client, pollCache, event) => {
1640
1683
  );
1641
1684
  return messages5;
1642
1685
  };
1643
- var toPollUnvoteMessages = async (client, pollCache, event) => {
1686
+ var toPollUnvoteMessages = async (client, pollCache, event, phone) => {
1644
1687
  const senderAddress = event.actor.address;
1645
1688
  if (!senderAddress) {
1646
1689
  return [];
@@ -1660,17 +1703,18 @@ var toPollUnvoteMessages = async (client, pollCache, event) => {
1660
1703
  chatGuid: chatGuidStr,
1661
1704
  deltas,
1662
1705
  event,
1706
+ phone,
1663
1707
  senderAddress
1664
1708
  });
1665
1709
  pollCache.commitActorSelection(pollId, senderAddress, [], event.at);
1666
1710
  return messages5;
1667
1711
  };
1668
- var toPollDeltaMessages = async (client, pollCache, event) => {
1712
+ var toPollDeltaMessages = async (client, pollCache, event, phone) => {
1669
1713
  if (isVotedPollEvent(event)) {
1670
- return toPollVoteMessages(client, pollCache, event);
1714
+ return toPollVoteMessages(client, pollCache, event, phone);
1671
1715
  }
1672
1716
  if (isUnvotedPollEvent(event)) {
1673
- return toPollUnvoteMessages(client, pollCache, event);
1717
+ return toPollUnvoteMessages(client, pollCache, event, phone);
1674
1718
  }
1675
1719
  return [];
1676
1720
  };
@@ -1686,43 +1730,43 @@ var isRetryableIMessageStreamError = (error) => {
1686
1730
  }
1687
1731
  return false;
1688
1732
  };
1689
- var toMessageItem = async (client, event, cursor) => {
1733
+ var toMessageItem = async (client, event, phone, cursor) => {
1690
1734
  const id = event.message.guid;
1691
1735
  if (event.message.isFromMe) {
1692
1736
  return { cursor, id, values: [] };
1693
1737
  }
1694
1738
  const cache = getMessageCache(client);
1695
1739
  const target = event.message.associatedMessageGuid;
1696
- const values = target ? await toReactionMessages(client, cache, event, target) : await toInboundMessages(client, cache, event);
1740
+ const values = target ? await toReactionMessages(client, cache, event, target, phone) : await toInboundMessages(client, cache, event, phone);
1697
1741
  return { cursor, id, values };
1698
1742
  };
1699
- var messageStream = (client) => resumableOrderedStream({
1743
+ var messageStream = (client, phone) => resumableOrderedStream({
1700
1744
  fetchMissed: (cursor, { limit }) => client.messages.fetchMissed(cursor, { limit }),
1701
1745
  isRetryableError: isRetryableIMessageStreamError,
1702
- processLive: (event) => toMessageItem(client, event, event.cursor),
1703
- processMissed: (message) => toMessageItem(client, receivedEventFromMessage(message)),
1746
+ processLive: (event) => toMessageItem(client, event, phone, event.cursor),
1747
+ processMissed: (message) => toMessageItem(client, receivedEventFromMessage(message), phone),
1704
1748
  subscribeLive: () => client.messages.subscribe("message.received")
1705
1749
  });
1706
1750
  var logPollStreamError = (error) => {
1707
1751
  console.error("[spectrum-ts][imessage][poll] stream failed", error);
1708
1752
  };
1709
- var emitPollMessages = async (client, pollCache, event, emit) => {
1753
+ var emitPollMessages = async (client, pollCache, event, phone, emit) => {
1710
1754
  cachePollEvent(pollCache, event);
1711
1755
  if (event.actor.isFromMe) {
1712
1756
  return;
1713
1757
  }
1714
- const messages5 = await toPollDeltaMessages(client, pollCache, event);
1758
+ const messages5 = await toPollDeltaMessages(client, pollCache, event, phone);
1715
1759
  for (const vote of messages5) {
1716
1760
  await emit(vote);
1717
1761
  }
1718
1762
  };
1719
- var runPollSubscription = async (client, pollCache, subscription, emit, onEvent) => {
1763
+ var runPollSubscription = async (client, pollCache, subscription, phone, emit, onEvent) => {
1720
1764
  for await (const event of subscription) {
1721
1765
  onEvent();
1722
- await emitPollMessages(client, pollCache, event, emit);
1766
+ await emitPollMessages(client, pollCache, event, phone, emit);
1723
1767
  }
1724
1768
  };
1725
- var pollStream = (client, pollCache) => stream((emit, end) => {
1769
+ var pollStream = (client, pollCache, phone) => stream((emit, end) => {
1726
1770
  let active = client.polls.subscribe();
1727
1771
  let closed = false;
1728
1772
  let retryDelayMs = RECONNECT_INITIAL_DELAY_MS;
@@ -1750,9 +1794,16 @@ var pollStream = (client, pollCache) => stream((emit, end) => {
1750
1794
  const pump = (async () => {
1751
1795
  while (!closed) {
1752
1796
  try {
1753
- await runPollSubscription(client, pollCache, active, emit, () => {
1754
- retryDelayMs = RECONNECT_INITIAL_DELAY_MS;
1755
- });
1797
+ await runPollSubscription(
1798
+ client,
1799
+ pollCache,
1800
+ active,
1801
+ phone,
1802
+ emit,
1803
+ () => {
1804
+ retryDelayMs = RECONNECT_INITIAL_DELAY_MS;
1805
+ }
1806
+ );
1756
1807
  } catch (e) {
1757
1808
  if (!closed) {
1758
1809
  logPollStreamError(e);
@@ -1775,12 +1826,17 @@ var pollStream = (client, pollCache) => stream((emit, end) => {
1775
1826
  await pump;
1776
1827
  };
1777
1828
  });
1778
- var clientStream = (client, pollCache) => {
1779
- return mergeStreams([messageStream(client), pollStream(client, pollCache)]);
1829
+ var clientStream = (client, pollCache, phone) => {
1830
+ return mergeStreams([
1831
+ messageStream(client, phone),
1832
+ pollStream(client, pollCache, phone)
1833
+ ]);
1780
1834
  };
1781
1835
  var messages3 = (clients) => {
1782
1836
  const pollCache = getPollCache(clients);
1783
- return mergeStreams(clients.map((client) => clientStream(client, pollCache)));
1837
+ return mergeStreams(
1838
+ clients.map((entry) => clientStream(entry.client, pollCache, entry.phone))
1839
+ );
1784
1840
  };
1785
1841
 
1786
1842
  // src/providers/imessage/remote/typing.ts
@@ -1794,43 +1850,52 @@ var stopTyping = async (remote, spaceId) => {
1794
1850
 
1795
1851
  // src/providers/imessage/remote/api.ts
1796
1852
  var messages4 = (clients) => messages3(clients);
1797
- var startTyping2 = async (clients, spaceId) => {
1798
- const remote = firstRemoteClient(clients);
1799
- if (!remote) {
1800
- return;
1801
- }
1853
+ var startTyping2 = async (remote, spaceId) => {
1802
1854
  await startTyping(remote, spaceId);
1803
1855
  };
1804
- var stopTyping2 = async (clients, spaceId) => {
1805
- const remote = firstRemoteClient(clients);
1806
- if (!remote) {
1807
- return;
1808
- }
1856
+ var stopTyping2 = async (remote, spaceId) => {
1809
1857
  await stopTyping(remote, spaceId);
1810
1858
  };
1811
- var send4 = async (clients, spaceId, content) => send3(primaryRemoteClient(clients), spaceId, content);
1812
- var replyToMessage2 = async (clients, spaceId, msgId, content) => replyToMessage(primaryRemoteClient(clients), spaceId, msgId, content);
1813
- var editMessage2 = async (clients, spaceId, msgId, content) => editMessage(primaryRemoteClient(clients), spaceId, msgId, content);
1814
- var reactToMessage2 = async (clients, spaceId, target, reaction) => {
1815
- const remote = firstRemoteClient(clients);
1816
- if (!remote) {
1817
- return;
1818
- }
1859
+ var send4 = async (remote, spaceId, content) => send3(remote, spaceId, content);
1860
+ var replyToMessage2 = async (remote, spaceId, msgId, content) => replyToMessage(remote, spaceId, msgId, content);
1861
+ var editMessage2 = async (remote, spaceId, msgId, content) => editMessage(remote, spaceId, msgId, content);
1862
+ var reactToMessage2 = async (remote, spaceId, target, reaction) => {
1819
1863
  await reactToMessage(remote, spaceId, target, reaction);
1820
1864
  };
1821
- var getMessage4 = async (clients, spaceId, msgId) => {
1822
- const remote = firstRemoteClient(clients);
1823
- if (!remote) {
1824
- return;
1865
+ var getMessage4 = async (remote, spaceId, msgId, phone) => getMessage3(remote, spaceId, msgId, phone);
1866
+
1867
+ // src/providers/imessage/remote/client.ts
1868
+ var availablePhones = (clients) => clients.map((c) => c.phone);
1869
+ var clientForPhone = (clients, phone) => {
1870
+ const entry = clients.find((c) => c.phone === phone);
1871
+ if (!entry) {
1872
+ const list = availablePhones(clients).join(", ") || "<none>";
1873
+ throw new Error(
1874
+ `No iMessage client serves phone ${phone}. Available: ${list}`
1875
+ );
1825
1876
  }
1826
- return getMessage3(remote, spaceId, msgId);
1877
+ return entry.client;
1878
+ };
1879
+ var randomPhone = (clients) => {
1880
+ if (clients.length === 0) {
1881
+ throw new Error("No iMessage phones configured for this account");
1882
+ }
1883
+ const entry = clients[Math.floor(Math.random() * clients.length)];
1884
+ if (!entry) {
1885
+ throw new Error("No iMessage phones configured for this account");
1886
+ }
1887
+ return entry.phone;
1827
1888
  };
1828
1889
 
1829
1890
  // src/providers/imessage/types.ts
1830
1891
  import { IMessageSDK } from "@photon-ai/imessage-kit";
1831
1892
  import z2 from "zod";
1832
1893
  var isLocal = (client) => client instanceof IMessageSDK;
1833
- var clientEntry = z2.object({ address: z2.string(), token: z2.string() });
1894
+ var clientEntry = z2.object({
1895
+ address: z2.string(),
1896
+ token: z2.string(),
1897
+ phone: z2.string()
1898
+ });
1834
1899
  var configSchema = z2.union([
1835
1900
  z2.object({ local: z2.literal(true) }),
1836
1901
  z2.object({
@@ -1841,7 +1906,11 @@ var configSchema = z2.union([
1841
1906
  var userSchema = z2.object({});
1842
1907
  var spaceSchema = z2.object({
1843
1908
  id: z2.string(),
1844
- type: z2.enum(["dm", "group"])
1909
+ type: z2.enum(["dm", "group"]),
1910
+ phone: z2.string()
1911
+ });
1912
+ var spaceParamsSchema = z2.object({
1913
+ phone: z2.string().optional()
1845
1914
  });
1846
1915
  var messageSchema = z2.object({
1847
1916
  partIndex: z2.number().int().nonnegative().optional(),
@@ -1857,11 +1926,48 @@ var imessage = definePlatform("iMessage", {
1857
1926
  message: MessageEffect2
1858
1927
  }
1859
1928
  },
1929
+ lifecycle: {
1930
+ createClient: async ({
1931
+ config,
1932
+ projectId,
1933
+ projectSecret
1934
+ }) => {
1935
+ if (config.local) {
1936
+ return new IMessageSDK2();
1937
+ }
1938
+ if (config.clients) {
1939
+ const entries = Array.isArray(config.clients) ? config.clients : [config.clients];
1940
+ return entries.map((e) => ({
1941
+ phone: e.phone,
1942
+ client: createClient2({
1943
+ address: e.address,
1944
+ tls: true,
1945
+ token: e.token
1946
+ })
1947
+ }));
1948
+ }
1949
+ if (!(projectId && projectSecret)) {
1950
+ throw new Error(
1951
+ "iMessage requires projectId and projectSecret. Either pass credentials to Spectrum(), use local mode: imessage.config({ local: true }), or provide explicit client config: imessage.config({ clients: [...] })"
1952
+ );
1953
+ }
1954
+ return await createCloudClients(projectId, projectSecret);
1955
+ },
1956
+ destroyClient: async ({ client }) => {
1957
+ if (isLocal(client)) {
1958
+ await client.close();
1959
+ return;
1960
+ }
1961
+ await disposeCloudAuth(client);
1962
+ await Promise.all(client.map((entry) => entry.client.close()));
1963
+ }
1964
+ },
1860
1965
  user: {
1861
1966
  resolve: async ({ input }) => ({ id: input.userID })
1862
1967
  },
1863
1968
  space: {
1864
1969
  schema: spaceSchema,
1970
+ params: spaceParamsSchema,
1865
1971
  resolve: async ({ input, client }) => {
1866
1972
  if (isLocal(client)) {
1867
1973
  throw UnsupportedError.action(
@@ -1873,55 +1979,26 @@ var imessage = definePlatform("iMessage", {
1873
1979
  if (input.users.length === 0) {
1874
1980
  throw new Error("iMessage space creation requires at least one user");
1875
1981
  }
1982
+ if (client.length === 0) {
1983
+ throw new Error("No iMessage clients configured");
1984
+ }
1985
+ const phone = input.params?.phone ?? randomPhone(client);
1986
+ const remote = clientForPhone(client, phone);
1876
1987
  const addresses = input.users.map((u) => u.id);
1877
1988
  if (input.users.length === 1) {
1878
1989
  return {
1879
1990
  id: directChat(addresses[0] ?? ""),
1880
- type: "dm"
1991
+ type: "dm",
1992
+ phone
1881
1993
  };
1882
1994
  }
1883
- const remote = client[0];
1884
- if (!remote) {
1885
- throw new Error("No remote iMessage client available");
1886
- }
1887
1995
  const { chat } = await remote.chats.create(addresses);
1888
- return { id: chat.guid, type: "group" };
1996
+ return { id: chat.guid, type: "group", phone };
1889
1997
  }
1890
1998
  },
1891
1999
  message: {
1892
2000
  schema: messageSchema
1893
2001
  },
1894
- lifecycle: {
1895
- createClient: async ({
1896
- config,
1897
- projectId,
1898
- projectSecret
1899
- }) => {
1900
- if (config.local) {
1901
- return new IMessageSDK2();
1902
- }
1903
- if (config.clients) {
1904
- const entries = Array.isArray(config.clients) ? config.clients : [config.clients];
1905
- return entries.map(
1906
- (e) => createClient2({ address: e.address, tls: true, token: e.token })
1907
- );
1908
- }
1909
- if (!(projectId && projectSecret)) {
1910
- throw new Error(
1911
- "iMessage requires projectId and projectSecret. Either pass credentials to Spectrum(), use local mode: imessage.config({ local: true }), or provide explicit client config: imessage.config({ clients: [...] })"
1912
- );
1913
- }
1914
- return await createCloudClients(projectId, projectSecret);
1915
- },
1916
- destroyClient: async ({ client }) => {
1917
- if (isLocal(client)) {
1918
- await client.close();
1919
- return;
1920
- }
1921
- await disposeCloudAuth(client);
1922
- await Promise.all(client.map((c) => c.close()));
1923
- }
1924
- },
1925
2002
  events: {
1926
2003
  messages: ({ client }) => isLocal(client) ? messages2(client) : messages4(client)
1927
2004
  },
@@ -1930,19 +2007,22 @@ var imessage = definePlatform("iMessage", {
1930
2007
  if (isLocal(client)) {
1931
2008
  return await send2(client, space.id, content);
1932
2009
  }
1933
- return await send4(client, space.id, content);
2010
+ const remote = clientForPhone(client, space.phone);
2011
+ return await send4(remote, space.id, content);
1934
2012
  },
1935
2013
  startTyping: async ({ space, client }) => {
1936
2014
  if (isLocal(client)) {
1937
2015
  return;
1938
2016
  }
1939
- await startTyping2(client, space.id);
2017
+ const remote = clientForPhone(client, space.phone);
2018
+ await startTyping2(remote, space.id);
1940
2019
  },
1941
2020
  stopTyping: async ({ space, client }) => {
1942
2021
  if (isLocal(client)) {
1943
2022
  return;
1944
2023
  }
1945
- await stopTyping2(client, space.id);
2024
+ const remote = clientForPhone(client, space.phone);
2025
+ await stopTyping2(remote, space.id);
1946
2026
  },
1947
2027
  reactToMessage: async ({ space, target, reaction, client }) => {
1948
2028
  if (isLocal(client)) {
@@ -1955,8 +2035,9 @@ var imessage = definePlatform("iMessage", {
1955
2035
  "iMessage polls do not support reactions"
1956
2036
  );
1957
2037
  }
2038
+ const remote = clientForPhone(client, space.phone);
1958
2039
  await reactToMessage2(
1959
- client,
2040
+ remote,
1960
2041
  space.id,
1961
2042
  target,
1962
2043
  reaction
@@ -1973,19 +2054,22 @@ var imessage = definePlatform("iMessage", {
1973
2054
  "iMessage polls do not support replies"
1974
2055
  );
1975
2056
  }
1976
- return await replyToMessage2(client, space.id, messageId, content);
2057
+ const remote = clientForPhone(client, space.phone);
2058
+ return await replyToMessage2(remote, space.id, messageId, content);
1977
2059
  },
1978
2060
  editMessage: async ({ space, messageId, content, client }) => {
1979
2061
  if (isLocal(client)) {
1980
2062
  throw UnsupportedError.action("edit", "iMessage (local mode)");
1981
2063
  }
1982
- await editMessage2(client, space.id, messageId, content);
2064
+ const remote = clientForPhone(client, space.phone);
2065
+ await editMessage2(remote, space.id, messageId, content);
1983
2066
  },
1984
2067
  getMessage: async ({ space, messageId, client }) => {
1985
2068
  if (isLocal(client)) {
1986
2069
  return getMessage2(client, messageId);
1987
2070
  }
1988
- return getMessage4(client, space.id, messageId);
2071
+ const remote = clientForPhone(client, space.phone);
2072
+ return getMessage4(remote, space.id, messageId, space.phone);
1989
2073
  }
1990
2074
  }
1991
2075
  });