spectrum-ts 1.2.1 → 1.4.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,62 @@ 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";
81
+
82
+ // src/providers/imessage/types.ts
83
+ import { IMessageSDK } from "@photon-ai/imessage-kit";
84
+ import z2 from "zod";
85
+ var SHARED_PHONE = "shared";
86
+ var isLocal = (client) => client instanceof IMessageSDK;
87
+ var clientEntry = z2.object({
88
+ address: z2.string(),
89
+ token: z2.string(),
90
+ phone: z2.string()
91
+ });
92
+ var configSchema = z2.union([
93
+ z2.object({ local: z2.literal(true) }),
94
+ z2.object({
95
+ local: z2.literal(false).optional().default(false),
96
+ clients: clientEntry.or(z2.array(clientEntry)).optional()
97
+ })
98
+ ]);
99
+ var userSchema = z2.object({});
100
+ var spaceSchema = z2.object({
101
+ id: z2.string(),
102
+ type: z2.enum(["dm", "group"]),
103
+ phone: z2.string()
104
+ });
105
+ var spaceParamsSchema = z2.object({
106
+ phone: z2.string().optional()
107
+ });
108
+ var messageSchema = z2.object({
109
+ partIndex: z2.number().int().nonnegative().optional(),
110
+ parentId: z2.string().optional()
111
+ });
112
+
113
+ // src/providers/imessage/auth.ts
83
114
  var RENEWAL_RATIO = 0.8;
84
115
  var EXPIRY_BUFFER_MS = 3e4;
85
116
  var RETRY_DELAY_MS = 3e4;
86
117
  var cloudAuthState = /* @__PURE__ */ new WeakMap();
118
+ var requirePhone = (data, instanceId) => {
119
+ const phone = data.numbers?.[instanceId];
120
+ if (!phone) {
121
+ throw new Error(`iMessage instance ${instanceId} has no phone assigned`);
122
+ }
123
+ return phone;
124
+ };
87
125
  async function createCloudClients(projectId, projectSecret) {
88
126
  let tokenData = await cloud.issueImessageTokens(projectId, projectSecret);
89
127
  let tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
90
128
  let disposed = false;
91
129
  let renewalTimer;
130
+ const records = [];
131
+ const syncPhones = (data) => {
132
+ for (const { entry, instanceId } of records) {
133
+ entry.phone = requirePhone(data, instanceId);
134
+ }
135
+ };
92
136
  const scheduleRenewal = () => {
93
137
  if (disposed) {
94
138
  return;
@@ -99,6 +143,9 @@ async function createCloudClients(projectId, projectSecret) {
99
143
  try {
100
144
  tokenData = await cloud.issueImessageTokens(projectId, projectSecret);
101
145
  tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
146
+ if (tokenData.type === "dedicated") {
147
+ syncPhones(tokenData);
148
+ }
102
149
  scheduleRenewal();
103
150
  } catch {
104
151
  renewalTimer = setTimeout(() => scheduleRenewal(), RETRY_DELAY_MS);
@@ -114,13 +161,17 @@ async function createCloudClients(projectId, projectSecret) {
114
161
  }
115
162
  tokenData = await cloud.issueImessageTokens(projectId, projectSecret);
116
163
  tokenExpiresAt = Date.now() + tokenData.expiresIn * 1e3;
164
+ if (tokenData.type === "dedicated") {
165
+ syncPhones(tokenData);
166
+ }
117
167
  scheduleRenewal();
118
168
  };
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({
169
+ if (tokenData.type === "shared") {
170
+ const address = process.env.SPECTRUM_IMESSAGE_ADDRESS ?? "imessage.spectrum.photon.codes:443";
171
+ const entries2 = [
172
+ {
173
+ phone: SHARED_PHONE,
174
+ client: createClient({
124
175
  address,
125
176
  tls: true,
126
177
  token: async () => {
@@ -128,10 +179,24 @@ async function createCloudClients(projectId, projectSecret) {
128
179
  return tokenData.token;
129
180
  }
130
181
  })
131
- ];
132
- }
133
- return Object.entries(tokenData.auth).map(
134
- ([instanceId, token]) => createClient({
182
+ }
183
+ ];
184
+ cloudAuthState.set(entries2, {
185
+ dispose: () => {
186
+ disposed = true;
187
+ if (renewalTimer !== void 0) {
188
+ clearTimeout(renewalTimer);
189
+ renewalTimer = void 0;
190
+ }
191
+ }
192
+ });
193
+ return entries2;
194
+ }
195
+ const dedicated = tokenData;
196
+ for (const [instanceId, token] of Object.entries(dedicated.auth)) {
197
+ const entry = {
198
+ phone: requirePhone(dedicated, instanceId),
199
+ client: createClient({
135
200
  address: `${instanceId}.imsg.photon.codes:443`,
136
201
  tls: true,
137
202
  token: async () => {
@@ -140,10 +205,11 @@ async function createCloudClients(projectId, projectSecret) {
140
205
  return data.auth[instanceId] ?? token;
141
206
  }
142
207
  })
143
- );
144
- };
145
- const clients = buildClients();
146
- cloudAuthState.set(clients, {
208
+ };
209
+ records.push({ entry, instanceId });
210
+ }
211
+ const entries = records.map((r) => r.entry);
212
+ cloudAuthState.set(entries, {
147
213
  dispose: () => {
148
214
  disposed = true;
149
215
  if (renewalTimer !== void 0) {
@@ -152,7 +218,7 @@ async function createCloudClients(projectId, projectSecret) {
152
218
  }
153
219
  }
154
220
  });
155
- return clients;
221
+ return entries;
156
222
  }
157
223
  async function disposeCloudAuth(clients) {
158
224
  const auth = cloudAuthState.get(clients);
@@ -230,7 +296,12 @@ var toMessages = async (message) => {
230
296
  }
231
297
  const base = {
232
298
  sender: { id: message.participant ?? "" },
233
- space: { id: chatId, type: chatKind === "group" ? "group" : "dm" },
299
+ // Local mode has no concept of "which-of-my-phones"; phone is empty.
300
+ space: {
301
+ id: chatId,
302
+ type: chatKind === "group" ? "group" : "dm",
303
+ phone: ""
304
+ },
234
305
  timestamp: message.createdAt
235
306
  };
236
307
  if (message.attachments.length > 0) {
@@ -337,17 +408,6 @@ var messages2 = (client) => messages(client);
337
408
  var send2 = async (client, spaceId, content) => send(client, spaceId, content);
338
409
  var getMessage2 = async (client, id) => getMessage(client, id);
339
410
 
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
411
  // src/providers/imessage/remote/inbound.ts
352
412
  import {
353
413
  messageGuid,
@@ -520,13 +580,14 @@ var isIMessageMessage = (value) => {
520
580
  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
581
  };
522
582
  var asProviderGroup = (items) => groupSchema.parse({ type: "group", items });
523
- var buildMessageBase = (message, chatGuidHint, timestamp) => {
583
+ var buildMessageBase = (message, chatGuidHint, timestamp, phone) => {
524
584
  const chat = resolveChatGuid(message, chatGuidHint);
525
585
  return {
526
586
  sender: { id: resolveSenderId(message) },
527
587
  space: {
528
588
  id: chat,
529
- type: chat.includes(";+;") ? "group" : "dm"
589
+ type: chat.includes(";+;") ? "group" : "dm",
590
+ phone
530
591
  },
531
592
  timestamp
532
593
  };
@@ -581,10 +642,10 @@ var toRichlinkMessage = (message, base, id) => {
581
642
  };
582
643
  }
583
644
  };
584
- var rebuildFromAppleMessage = async (client, message, chatGuidHint) => {
645
+ var rebuildFromAppleMessage = async (client, message, phone, chatGuidHint) => {
585
646
  const messageGuidStr = message.guid;
586
647
  const timestamp = message.dateCreated ?? /* @__PURE__ */ new Date();
587
- const base = buildMessageBase(message, chatGuidHint, timestamp);
648
+ const base = buildMessageBase(message, chatGuidHint, timestamp, phone);
588
649
  if (message.attachments.length === 1) {
589
650
  const info = message.attachments[0];
590
651
  if (!info) {
@@ -636,8 +697,13 @@ var cacheMessage = (cache, message) => {
636
697
  }
637
698
  }
638
699
  };
639
- var toInboundMessages = async (client, cache, event) => {
640
- const base = buildMessageBase(event.message, event.chatGuid, event.timestamp);
700
+ var toInboundMessages = async (client, cache, event, phone) => {
701
+ const base = buildMessageBase(
702
+ event.message,
703
+ event.chatGuid,
704
+ event.timestamp,
705
+ phone
706
+ );
641
707
  const messageGuidStr = event.message.guid;
642
708
  if (getBalloonBundleId(event.message) === URL_BALLOON_BUNDLE_ID) {
643
709
  const msg2 = toRichlinkMessage(event.message, base, messageGuidStr);
@@ -694,7 +760,7 @@ var toInboundMessages = async (client, cache, event) => {
694
760
  cacheMessage(cache, msg);
695
761
  return [msg];
696
762
  };
697
- var getMessage3 = async (remote, spaceId, msgId) => {
763
+ var getMessage3 = async (remote, spaceId, msgId, phone) => {
698
764
  const cache = getMessageCache(remote);
699
765
  const cached = cache.get(msgId);
700
766
  if (cached) {
@@ -706,7 +772,12 @@ var getMessage3 = async (remote, spaceId, msgId) => {
706
772
  const fetched = await remote.messages.get(
707
773
  messageGuid(childRef.parentGuid)
708
774
  );
709
- const parent = await rebuildFromAppleMessage(remote, fetched, spaceId);
775
+ const parent = await rebuildFromAppleMessage(
776
+ remote,
777
+ fetched,
778
+ phone,
779
+ spaceId
780
+ );
710
781
  cacheMessage(cache, parent);
711
782
  if (parent.content.type !== "group") {
712
783
  return;
@@ -722,7 +793,12 @@ var getMessage3 = async (remote, spaceId, msgId) => {
722
793
  }
723
794
  try {
724
795
  const fetched = await remote.messages.get(messageGuid(msgId));
725
- const rebuilt = await rebuildFromAppleMessage(remote, fetched, spaceId);
796
+ const rebuilt = await rebuildFromAppleMessage(
797
+ remote,
798
+ fetched,
799
+ phone,
800
+ spaceId
801
+ );
726
802
  cacheMessage(cache, rebuilt);
727
803
  return rebuilt;
728
804
  } catch (err) {
@@ -785,12 +861,12 @@ var asProviderReaction = (emoji, target) => reactionSchema.parse({
785
861
  target,
786
862
  type: "reaction"
787
863
  });
788
- var resolveReactionTarget = async (client, cache, strippedGuid, partIndex) => {
864
+ var resolveReactionTarget = async (client, cache, strippedGuid, partIndex, phone) => {
789
865
  let candidate = cache.get(strippedGuid);
790
866
  if (!candidate) {
791
867
  try {
792
868
  const fetched = await client.messages.get(messageGuid2(strippedGuid));
793
- candidate = await rebuildFromAppleMessage(client, fetched);
869
+ candidate = await rebuildFromAppleMessage(client, fetched, phone);
794
870
  cacheMessage(cache, candidate);
795
871
  } catch {
796
872
  return;
@@ -806,7 +882,7 @@ var resolveReactionTarget = async (client, cache, strippedGuid, partIndex) => {
806
882
  }
807
883
  return candidate;
808
884
  };
809
- var toReactionMessages = async (client, cache, event, target) => {
885
+ var toReactionMessages = async (client, cache, event, target, phone) => {
810
886
  const type = getAssociatedMessageType(event.message);
811
887
  if (type && isTapbackRemoval(type)) {
812
888
  return [];
@@ -823,7 +899,8 @@ var toReactionMessages = async (client, cache, event, target) => {
823
899
  client,
824
900
  cache,
825
901
  strippedGuid,
826
- partIndex
902
+ partIndex,
903
+ phone
827
904
  );
828
905
  if (!resolved) {
829
906
  return [];
@@ -832,7 +909,12 @@ var toReactionMessages = async (client, cache, event, target) => {
832
909
  if (typeof messageId !== "string" || messageId.length === 0) {
833
910
  return [];
834
911
  }
835
- const base = buildMessageBase(event.message, event.chatGuid, event.timestamp);
912
+ const base = buildMessageBase(
913
+ event.message,
914
+ event.chatGuid,
915
+ event.timestamp,
916
+ phone
917
+ );
836
918
  return [
837
919
  {
838
920
  ...base,
@@ -1545,7 +1627,8 @@ var buildPollOptionMessage = (input) => {
1545
1627
  sender: { id: input.senderAddress },
1546
1628
  space: {
1547
1629
  id: input.chatGuid,
1548
- type: input.chatGuid.includes(";+;") ? "group" : "dm"
1630
+ type: input.chatGuid.includes(";+;") ? "group" : "dm",
1631
+ phone: input.phone
1549
1632
  },
1550
1633
  timestamp: input.event.at,
1551
1634
  content: asPollOption({
@@ -1563,6 +1646,7 @@ var buildPollOptionMessages = (input) => {
1563
1646
  chatGuid: input.chatGuid,
1564
1647
  event: input.event,
1565
1648
  optionId: delta.optionId,
1649
+ phone: input.phone,
1566
1650
  selected: delta.selected,
1567
1651
  senderAddress: input.senderAddress
1568
1652
  });
@@ -1587,7 +1671,7 @@ var refreshPollMetadata = async (client, pollCache, event, fallbackOptionIds) =>
1587
1671
  poll: refreshed
1588
1672
  };
1589
1673
  };
1590
- var toPollVoteMessages = async (client, pollCache, event) => {
1674
+ var toPollVoteMessages = async (client, pollCache, event, phone) => {
1591
1675
  const senderAddress = event.actor.address;
1592
1676
  if (!senderAddress) {
1593
1677
  return [];
@@ -1630,6 +1714,7 @@ var toPollVoteMessages = async (client, pollCache, event) => {
1630
1714
  chatGuid: chatGuidStr,
1631
1715
  deltas,
1632
1716
  event,
1717
+ phone,
1633
1718
  senderAddress
1634
1719
  });
1635
1720
  pollCache.commitActorSelection(
@@ -1640,7 +1725,7 @@ var toPollVoteMessages = async (client, pollCache, event) => {
1640
1725
  );
1641
1726
  return messages5;
1642
1727
  };
1643
- var toPollUnvoteMessages = async (client, pollCache, event) => {
1728
+ var toPollUnvoteMessages = async (client, pollCache, event, phone) => {
1644
1729
  const senderAddress = event.actor.address;
1645
1730
  if (!senderAddress) {
1646
1731
  return [];
@@ -1660,17 +1745,18 @@ var toPollUnvoteMessages = async (client, pollCache, event) => {
1660
1745
  chatGuid: chatGuidStr,
1661
1746
  deltas,
1662
1747
  event,
1748
+ phone,
1663
1749
  senderAddress
1664
1750
  });
1665
1751
  pollCache.commitActorSelection(pollId, senderAddress, [], event.at);
1666
1752
  return messages5;
1667
1753
  };
1668
- var toPollDeltaMessages = async (client, pollCache, event) => {
1754
+ var toPollDeltaMessages = async (client, pollCache, event, phone) => {
1669
1755
  if (isVotedPollEvent(event)) {
1670
- return toPollVoteMessages(client, pollCache, event);
1756
+ return toPollVoteMessages(client, pollCache, event, phone);
1671
1757
  }
1672
1758
  if (isUnvotedPollEvent(event)) {
1673
- return toPollUnvoteMessages(client, pollCache, event);
1759
+ return toPollUnvoteMessages(client, pollCache, event, phone);
1674
1760
  }
1675
1761
  return [];
1676
1762
  };
@@ -1686,43 +1772,43 @@ var isRetryableIMessageStreamError = (error) => {
1686
1772
  }
1687
1773
  return false;
1688
1774
  };
1689
- var toMessageItem = async (client, event, cursor) => {
1775
+ var toMessageItem = async (client, event, phone, cursor) => {
1690
1776
  const id = event.message.guid;
1691
1777
  if (event.message.isFromMe) {
1692
1778
  return { cursor, id, values: [] };
1693
1779
  }
1694
1780
  const cache = getMessageCache(client);
1695
1781
  const target = event.message.associatedMessageGuid;
1696
- const values = target ? await toReactionMessages(client, cache, event, target) : await toInboundMessages(client, cache, event);
1782
+ const values = target ? await toReactionMessages(client, cache, event, target, phone) : await toInboundMessages(client, cache, event, phone);
1697
1783
  return { cursor, id, values };
1698
1784
  };
1699
- var messageStream = (client) => resumableOrderedStream({
1785
+ var messageStream = (client, phone) => resumableOrderedStream({
1700
1786
  fetchMissed: (cursor, { limit }) => client.messages.fetchMissed(cursor, { limit }),
1701
1787
  isRetryableError: isRetryableIMessageStreamError,
1702
- processLive: (event) => toMessageItem(client, event, event.cursor),
1703
- processMissed: (message) => toMessageItem(client, receivedEventFromMessage(message)),
1788
+ processLive: (event) => toMessageItem(client, event, phone, event.cursor),
1789
+ processMissed: (message) => toMessageItem(client, receivedEventFromMessage(message), phone),
1704
1790
  subscribeLive: () => client.messages.subscribe("message.received")
1705
1791
  });
1706
1792
  var logPollStreamError = (error) => {
1707
1793
  console.error("[spectrum-ts][imessage][poll] stream failed", error);
1708
1794
  };
1709
- var emitPollMessages = async (client, pollCache, event, emit) => {
1795
+ var emitPollMessages = async (client, pollCache, event, phone, emit) => {
1710
1796
  cachePollEvent(pollCache, event);
1711
1797
  if (event.actor.isFromMe) {
1712
1798
  return;
1713
1799
  }
1714
- const messages5 = await toPollDeltaMessages(client, pollCache, event);
1800
+ const messages5 = await toPollDeltaMessages(client, pollCache, event, phone);
1715
1801
  for (const vote of messages5) {
1716
1802
  await emit(vote);
1717
1803
  }
1718
1804
  };
1719
- var runPollSubscription = async (client, pollCache, subscription, emit, onEvent) => {
1805
+ var runPollSubscription = async (client, pollCache, subscription, phone, emit, onEvent) => {
1720
1806
  for await (const event of subscription) {
1721
1807
  onEvent();
1722
- await emitPollMessages(client, pollCache, event, emit);
1808
+ await emitPollMessages(client, pollCache, event, phone, emit);
1723
1809
  }
1724
1810
  };
1725
- var pollStream = (client, pollCache) => stream((emit, end) => {
1811
+ var pollStream = (client, pollCache, phone) => stream((emit, end) => {
1726
1812
  let active = client.polls.subscribe();
1727
1813
  let closed = false;
1728
1814
  let retryDelayMs = RECONNECT_INITIAL_DELAY_MS;
@@ -1750,9 +1836,16 @@ var pollStream = (client, pollCache) => stream((emit, end) => {
1750
1836
  const pump = (async () => {
1751
1837
  while (!closed) {
1752
1838
  try {
1753
- await runPollSubscription(client, pollCache, active, emit, () => {
1754
- retryDelayMs = RECONNECT_INITIAL_DELAY_MS;
1755
- });
1839
+ await runPollSubscription(
1840
+ client,
1841
+ pollCache,
1842
+ active,
1843
+ phone,
1844
+ emit,
1845
+ () => {
1846
+ retryDelayMs = RECONNECT_INITIAL_DELAY_MS;
1847
+ }
1848
+ );
1756
1849
  } catch (e) {
1757
1850
  if (!closed) {
1758
1851
  logPollStreamError(e);
@@ -1775,12 +1868,17 @@ var pollStream = (client, pollCache) => stream((emit, end) => {
1775
1868
  await pump;
1776
1869
  };
1777
1870
  });
1778
- var clientStream = (client, pollCache) => {
1779
- return mergeStreams([messageStream(client), pollStream(client, pollCache)]);
1871
+ var clientStream = (client, pollCache, phone) => {
1872
+ return mergeStreams([
1873
+ messageStream(client, phone),
1874
+ pollStream(client, pollCache, phone)
1875
+ ]);
1780
1876
  };
1781
1877
  var messages3 = (clients) => {
1782
1878
  const pollCache = getPollCache(clients);
1783
- return mergeStreams(clients.map((client) => clientStream(client, pollCache)));
1879
+ return mergeStreams(
1880
+ clients.map((entry) => clientStream(entry.client, pollCache, entry.phone))
1881
+ );
1784
1882
  };
1785
1883
 
1786
1884
  // src/providers/imessage/remote/typing.ts
@@ -1794,59 +1892,53 @@ var stopTyping = async (remote, spaceId) => {
1794
1892
 
1795
1893
  // src/providers/imessage/remote/api.ts
1796
1894
  var messages4 = (clients) => messages3(clients);
1797
- var startTyping2 = async (clients, spaceId) => {
1798
- const remote = firstRemoteClient(clients);
1799
- if (!remote) {
1800
- return;
1801
- }
1895
+ var startTyping2 = async (remote, spaceId) => {
1802
1896
  await startTyping(remote, spaceId);
1803
1897
  };
1804
- var stopTyping2 = async (clients, spaceId) => {
1805
- const remote = firstRemoteClient(clients);
1806
- if (!remote) {
1807
- return;
1808
- }
1898
+ var stopTyping2 = async (remote, spaceId) => {
1809
1899
  await stopTyping(remote, spaceId);
1810
1900
  };
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
- }
1901
+ var send4 = async (remote, spaceId, content) => send3(remote, spaceId, content);
1902
+ var replyToMessage2 = async (remote, spaceId, msgId, content) => replyToMessage(remote, spaceId, msgId, content);
1903
+ var editMessage2 = async (remote, spaceId, msgId, content) => editMessage(remote, spaceId, msgId, content);
1904
+ var reactToMessage2 = async (remote, spaceId, target, reaction) => {
1819
1905
  await reactToMessage(remote, spaceId, target, reaction);
1820
1906
  };
1821
- var getMessage4 = async (clients, spaceId, msgId) => {
1822
- const remote = firstRemoteClient(clients);
1823
- if (!remote) {
1824
- return;
1907
+ var getMessage4 = async (remote, spaceId, msgId, phone) => getMessage3(remote, spaceId, msgId, phone);
1908
+
1909
+ // src/providers/imessage/remote/client.ts
1910
+ var isSharedMode = (clients) => clients.length === 1 && clients[0]?.phone === SHARED_PHONE;
1911
+ var availablePhones = (clients) => clients.map((c) => c.phone);
1912
+ var clientForPhone = (clients, phone) => {
1913
+ if (isSharedMode(clients)) {
1914
+ const entry2 = clients[0];
1915
+ if (!entry2) {
1916
+ throw new Error("No iMessage clients configured");
1917
+ }
1918
+ return entry2.client;
1919
+ }
1920
+ const entry = clients.find((c) => c.phone === phone);
1921
+ if (!entry) {
1922
+ const list = availablePhones(clients).join(", ") || "<none>";
1923
+ throw new Error(
1924
+ `No iMessage client serves phone ${phone}. Available: ${list}`
1925
+ );
1825
1926
  }
1826
- return getMessage3(remote, spaceId, msgId);
1927
+ return entry.client;
1928
+ };
1929
+ var randomPhone = (clients) => {
1930
+ if (clients.length === 0) {
1931
+ throw new Error("No iMessage phones configured for this account");
1932
+ }
1933
+ if (isSharedMode(clients)) {
1934
+ return SHARED_PHONE;
1935
+ }
1936
+ const entry = clients[Math.floor(Math.random() * clients.length)];
1937
+ if (!entry) {
1938
+ throw new Error("No iMessage phones configured for this account");
1939
+ }
1940
+ return entry.phone;
1827
1941
  };
1828
-
1829
- // src/providers/imessage/types.ts
1830
- import { IMessageSDK } from "@photon-ai/imessage-kit";
1831
- import z2 from "zod";
1832
- var isLocal = (client) => client instanceof IMessageSDK;
1833
- var clientEntry = z2.object({ address: z2.string(), token: z2.string() });
1834
- var configSchema = z2.union([
1835
- z2.object({ local: z2.literal(true) }),
1836
- z2.object({
1837
- local: z2.literal(false).optional().default(false),
1838
- clients: clientEntry.or(z2.array(clientEntry)).optional()
1839
- })
1840
- ]);
1841
- var userSchema = z2.object({});
1842
- var spaceSchema = z2.object({
1843
- id: z2.string(),
1844
- type: z2.enum(["dm", "group"])
1845
- });
1846
- var messageSchema = z2.object({
1847
- partIndex: z2.number().int().nonnegative().optional(),
1848
- parentId: z2.string().optional()
1849
- });
1850
1942
 
1851
1943
  // src/providers/imessage/index.ts
1852
1944
  var isPollContent = (content) => content.type === "poll" || content.type === "poll_option";
@@ -1857,11 +1949,48 @@ var imessage = definePlatform("iMessage", {
1857
1949
  message: MessageEffect2
1858
1950
  }
1859
1951
  },
1952
+ lifecycle: {
1953
+ createClient: async ({
1954
+ config,
1955
+ projectId,
1956
+ projectSecret
1957
+ }) => {
1958
+ if (config.local) {
1959
+ return new IMessageSDK2();
1960
+ }
1961
+ if (config.clients) {
1962
+ const entries = Array.isArray(config.clients) ? config.clients : [config.clients];
1963
+ return entries.map((e) => ({
1964
+ phone: e.phone,
1965
+ client: createClient2({
1966
+ address: e.address,
1967
+ tls: true,
1968
+ token: e.token
1969
+ })
1970
+ }));
1971
+ }
1972
+ if (!(projectId && projectSecret)) {
1973
+ throw new Error(
1974
+ "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: [...] })"
1975
+ );
1976
+ }
1977
+ return await createCloudClients(projectId, projectSecret);
1978
+ },
1979
+ destroyClient: async ({ client }) => {
1980
+ if (isLocal(client)) {
1981
+ await client.close();
1982
+ return;
1983
+ }
1984
+ await disposeCloudAuth(client);
1985
+ await Promise.all(client.map((entry) => entry.client.close()));
1986
+ }
1987
+ },
1860
1988
  user: {
1861
1989
  resolve: async ({ input }) => ({ id: input.userID })
1862
1990
  },
1863
1991
  space: {
1864
1992
  schema: spaceSchema,
1993
+ params: spaceParamsSchema,
1865
1994
  resolve: async ({ input, client }) => {
1866
1995
  if (isLocal(client)) {
1867
1996
  throw UnsupportedError.action(
@@ -1873,55 +2002,26 @@ var imessage = definePlatform("iMessage", {
1873
2002
  if (input.users.length === 0) {
1874
2003
  throw new Error("iMessage space creation requires at least one user");
1875
2004
  }
2005
+ if (client.length === 0) {
2006
+ throw new Error("No iMessage clients configured");
2007
+ }
2008
+ const phone = isSharedMode(client) ? SHARED_PHONE : input.params?.phone ?? randomPhone(client);
2009
+ const remote = clientForPhone(client, phone);
1876
2010
  const addresses = input.users.map((u) => u.id);
1877
2011
  if (input.users.length === 1) {
1878
2012
  return {
1879
2013
  id: directChat(addresses[0] ?? ""),
1880
- type: "dm"
2014
+ type: "dm",
2015
+ phone
1881
2016
  };
1882
2017
  }
1883
- const remote = client[0];
1884
- if (!remote) {
1885
- throw new Error("No remote iMessage client available");
1886
- }
1887
2018
  const { chat } = await remote.chats.create(addresses);
1888
- return { id: chat.guid, type: "group" };
2019
+ return { id: chat.guid, type: "group", phone };
1889
2020
  }
1890
2021
  },
1891
2022
  message: {
1892
2023
  schema: messageSchema
1893
2024
  },
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
2025
  events: {
1926
2026
  messages: ({ client }) => isLocal(client) ? messages2(client) : messages4(client)
1927
2027
  },
@@ -1930,19 +2030,22 @@ var imessage = definePlatform("iMessage", {
1930
2030
  if (isLocal(client)) {
1931
2031
  return await send2(client, space.id, content);
1932
2032
  }
1933
- return await send4(client, space.id, content);
2033
+ const remote = clientForPhone(client, space.phone);
2034
+ return await send4(remote, space.id, content);
1934
2035
  },
1935
2036
  startTyping: async ({ space, client }) => {
1936
2037
  if (isLocal(client)) {
1937
2038
  return;
1938
2039
  }
1939
- await startTyping2(client, space.id);
2040
+ const remote = clientForPhone(client, space.phone);
2041
+ await startTyping2(remote, space.id);
1940
2042
  },
1941
2043
  stopTyping: async ({ space, client }) => {
1942
2044
  if (isLocal(client)) {
1943
2045
  return;
1944
2046
  }
1945
- await stopTyping2(client, space.id);
2047
+ const remote = clientForPhone(client, space.phone);
2048
+ await stopTyping2(remote, space.id);
1946
2049
  },
1947
2050
  reactToMessage: async ({ space, target, reaction, client }) => {
1948
2051
  if (isLocal(client)) {
@@ -1955,8 +2058,9 @@ var imessage = definePlatform("iMessage", {
1955
2058
  "iMessage polls do not support reactions"
1956
2059
  );
1957
2060
  }
2061
+ const remote = clientForPhone(client, space.phone);
1958
2062
  await reactToMessage2(
1959
- client,
2063
+ remote,
1960
2064
  space.id,
1961
2065
  target,
1962
2066
  reaction
@@ -1973,19 +2077,22 @@ var imessage = definePlatform("iMessage", {
1973
2077
  "iMessage polls do not support replies"
1974
2078
  );
1975
2079
  }
1976
- return await replyToMessage2(client, space.id, messageId, content);
2080
+ const remote = clientForPhone(client, space.phone);
2081
+ return await replyToMessage2(remote, space.id, messageId, content);
1977
2082
  },
1978
2083
  editMessage: async ({ space, messageId, content, client }) => {
1979
2084
  if (isLocal(client)) {
1980
2085
  throw UnsupportedError.action("edit", "iMessage (local mode)");
1981
2086
  }
1982
- await editMessage2(client, space.id, messageId, content);
2087
+ const remote = clientForPhone(client, space.phone);
2088
+ await editMessage2(remote, space.id, messageId, content);
1983
2089
  },
1984
2090
  getMessage: async ({ space, messageId, client }) => {
1985
2091
  if (isLocal(client)) {
1986
2092
  return getMessage2(client, messageId);
1987
2093
  }
1988
- return getMessage4(client, space.id, messageId);
2094
+ const remote = clientForPhone(client, space.phone);
2095
+ return getMessage4(remote, space.id, messageId, space.phone);
1989
2096
  }
1990
2097
  }
1991
2098
  });