@spectrum-ts/imessage 9.1.0 → 9.3.1

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.
package/dist/index.d.ts CHANGED
@@ -141,6 +141,7 @@ declare const customizedMiniAppSchema: z.ZodObject<{
141
141
  imageSubtitle: z.ZodOptional<z.ZodString>;
142
142
  summary: z.ZodOptional<z.ZodString>;
143
143
  }, z.core.$strip>;
144
+ live: z.ZodOptional<z.ZodBoolean>;
144
145
  teamId: z.ZodString;
145
146
  url: z.ZodURL;
146
147
  }, z.core.$strip>;
@@ -155,7 +156,8 @@ type CustomizedMiniAppInput = Omit<CustomizedMiniApp, "type" | "__platform">;
155
156
  * the recipient taps the card; the server constructs the matching
156
157
  * `MSMessageExtensionBalloonPlugin` plugin id from these values. `appStoreId`
157
158
  * is optional and only points recipients without the extension at its App
158
- * Store entry.
159
+ * Store entry. `live` is optional; when omitted, the remote server keeps the
160
+ * static layout preview visible.
159
161
  *
160
162
  * `space.send(customizedMiniApp(...))` is the canonical form.
161
163
  *
@@ -193,8 +195,25 @@ declare const spaceSchema: z.ZodObject<{
193
195
  }>;
194
196
  phone: z.ZodString;
195
197
  }, z.core.$strip>;
198
+ declare const miniAppCardSessionSchema: z.ZodObject<{
199
+ chatGuid: z.ZodString;
200
+ messageGuid: z.ZodString;
201
+ sessionId: z.ZodString;
202
+ targetMessageGuid: z.ZodString;
203
+ }, z.core.$strip>;
204
+ /**
205
+ * iMessage-specific per-message metadata surfaced on `IMessageMessage`.
206
+ * - `partIndex`: ordered part index within a multi-part message. Text and
207
+ * attachment parts both consume an index (0 for bare or single-part
208
+ * messages; 0..N-1 for a group's sub-items).
209
+ * - `parentId`: guid of the parent message for a group sub-item. Undefined
210
+ * when the message itself is the parent.
211
+ * - `miniAppCardSession`: stable handle returned by mini-app card sends and
212
+ * updates. It is required to update the card in place later.
213
+ */
196
214
  type IMessageMessage = SchemaMessage<typeof userSchema, typeof spaceSchema> & {
197
215
  direction?: "inbound" | "outbound";
216
+ miniAppCardSession?: z.infer<typeof miniAppCardSessionSchema>;
198
217
  partIndex?: number;
199
218
  parentId?: string;
200
219
  };
@@ -238,6 +257,12 @@ declare const imessage: import("@spectrum-ts/core").Platform<import("@spectrum-t
238
257
  type: "dm" | "group";
239
258
  phone: string;
240
259
  }, import("zod").ZodObject<{
260
+ miniAppCardSession: import("zod").ZodOptional<import("zod").ZodObject<{
261
+ chatGuid: import("zod").ZodString;
262
+ messageGuid: import("zod").ZodString;
263
+ sessionId: import("zod").ZodString;
264
+ targetMessageGuid: import("zod").ZodString;
265
+ }, import("zod/v4/core").$strip>>;
241
266
  partIndex: import("zod").ZodOptional<import("zod").ZodNumber>;
242
267
  parentId: import("zod").ZodOptional<import("zod").ZodString>;
243
268
  }, import("zod/v4/core").$strip>, IMessageMessage, undefined, {
package/dist/index.js CHANGED
@@ -124,6 +124,7 @@ const customizedMiniAppSchema = z.object({
124
124
  appStoreId: z.number().int().positive().optional(),
125
125
  extensionBundleId: z.string().nonempty(),
126
126
  layout: layoutSchema,
127
+ live: z.boolean().optional(),
127
128
  teamId: z.string(),
128
129
  url: z.url()
129
130
  });
@@ -141,7 +142,8 @@ const asCustomizedMiniApp = (input) => customizedMiniAppSchema.parse({
141
142
  * the recipient taps the card; the server constructs the matching
142
143
  * `MSMessageExtensionBalloonPlugin` plugin id from these values. `appStoreId`
143
144
  * is optional and only points recipients without the extension at its App
144
- * Store entry.
145
+ * Store entry. `live` is optional; when omitted, the remote server keeps the
146
+ * static layout preview visible.
145
147
  *
146
148
  * `space.send(customizedMiniApp(...))` is the canonical form.
147
149
  *
@@ -202,6 +204,12 @@ const spaceSchema = z.object({
202
204
  phone: z.string()
203
205
  });
204
206
  const spaceParamsSchema = z.object({ phone: z.string().optional() });
207
+ const miniAppCardSessionSchema = z.object({
208
+ chatGuid: z.string(),
209
+ messageGuid: z.string(),
210
+ sessionId: z.string(),
211
+ targetMessageGuid: z.string()
212
+ });
205
213
  /**
206
214
  * iMessage-specific per-message metadata surfaced on `IMessageMessage`.
207
215
  * - `partIndex`: ordered part index within a multi-part message. Text and
@@ -209,8 +217,11 @@ const spaceParamsSchema = z.object({ phone: z.string().optional() });
209
217
  * messages; 0..N-1 for a group's sub-items).
210
218
  * - `parentId`: guid of the parent message for a group sub-item. Undefined
211
219
  * when the message itself is the parent.
220
+ * - `miniAppCardSession`: stable handle returned by mini-app card sends and
221
+ * updates. It is required to update the card in place later.
212
222
  */
213
223
  const messageSchema = z.object({
224
+ miniAppCardSession: miniAppCardSessionSchema.optional(),
214
225
  partIndex: z.number().int().nonnegative().optional(),
215
226
  parentId: z.string().optional()
216
227
  });
@@ -789,6 +800,14 @@ const shareContactCard$1 = async (remote, spaceId) => {
789
800
  };
790
801
  //#endregion
791
802
  //#region src/remote/customized-mini-app.ts
803
+ const toProviderRecord = (result, content, spaceId) => ({
804
+ id: result.guid,
805
+ content,
806
+ direction: "outbound",
807
+ miniAppCardSession: result.miniAppCardSession,
808
+ space: { id: spaceId },
809
+ timestamp: result.dateCreated
810
+ });
792
811
  /**
793
812
  * Send a `CustomizedMiniApp` card to a remote iMessage chat.
794
813
  *
@@ -800,14 +819,10 @@ const shareContactCard$1 = async (remote, spaceId) => {
800
819
  */
801
820
  const sendCustomizedMiniApp$1 = async (remote, spaceId, content) => {
802
821
  const chat = toChatGuid(spaceId);
803
- const message = await remote.messages.sendCustomizedMiniApp(chat, content);
804
- return {
805
- id: message.guid,
806
- content,
807
- direction: "outbound",
808
- space: { id: spaceId },
809
- timestamp: message.dateCreated
810
- };
822
+ return toProviderRecord(await remote.messages.sendCustomizedMiniApp(chat, content), content, spaceId);
823
+ };
824
+ const updateCustomizedMiniApp$1 = async (remote, spaceId, session, content) => {
825
+ return toProviderRecord(await remote.messages.updateCustomizedMiniApp(session, content), content, spaceId);
811
826
  };
812
827
  //#endregion
813
828
  //#region src/remote/attachments.ts
@@ -1686,6 +1701,30 @@ const unsendMessage$1 = async (remote, spaceId, msgId) => {
1686
1701
  await remote.messages.unsend(toChatGuid(spaceId), toMessageGuid(childRef?.parentGuid ?? msgId), childRef ? { partIndex: childRef.partIndex } : void 0);
1687
1702
  };
1688
1703
  //#endregion
1704
+ //#region src/remote/client.ts
1705
+ const isSharedMode = (clients) => clients.length === 1 && clients[0]?.phone === "shared";
1706
+ const availablePhones = (clients) => clients.map((c) => c.phone);
1707
+ const clientForPhone = (clients, phone) => {
1708
+ if (isSharedMode(clients)) {
1709
+ const entry = clients[0];
1710
+ if (!entry) throw new Error("No iMessage clients configured");
1711
+ return entry.client;
1712
+ }
1713
+ const entry = clients.find((c) => c.phone === phone);
1714
+ if (!entry) {
1715
+ const list = availablePhones(clients).join(", ") || "<none>";
1716
+ throw new Error(`No iMessage client serves phone ${phone}. Available: ${list}`);
1717
+ }
1718
+ return entry.client;
1719
+ };
1720
+ const randomPhone = (clients) => {
1721
+ if (clients.length === 0) throw new Error("No iMessage phones configured for this account");
1722
+ if (isSharedMode(clients)) return SHARED_PHONE;
1723
+ const entry = clients[Math.floor(Math.random() * clients.length)];
1724
+ if (!entry) throw new Error("No iMessage phones configured for this account");
1725
+ return entry.phone;
1726
+ };
1727
+ //#endregion
1689
1728
  //#region src/remote/contact-share.ts
1690
1729
  const log$2 = createLogger("spectrum.imessage.contact");
1691
1730
  const SHARE_TTL_MS = 1440 * 60 * 1e3;
@@ -2088,18 +2127,19 @@ const groupStream = (client, phone, recover) => resumableOrderedStream({
2088
2127
  processMissed: (event) => event.type === "catchup.complete" ? Promise.resolve(toCatchUpCompleteItem(event)) : toGroupItem(client, event, phone, String(event.sequence)),
2089
2128
  subscribeLive: (cursor) => withClose(client.groups.subscribeEvents(), cursor)
2090
2129
  });
2091
- const clientStream = (client, pollCache, phone, onInbound, recover) => mergeStreams([
2092
- messageStream(client, phone, onInbound, recover),
2093
- pollStream(client, pollCache, phone, recover),
2094
- groupStream(client, phone, recover)
2095
- ]);
2130
+ const clientStream = (client, pollCache, phone, includeGroupEvents, onInbound, recover) => {
2131
+ const streams = [messageStream(client, phone, onInbound, recover), pollStream(client, pollCache, phone, recover)];
2132
+ if (includeGroupEvents) streams.push(groupStream(client, phone, recover));
2133
+ return mergeStreams(streams);
2134
+ };
2096
2135
  const messages$1 = (clients, projectConfig) => {
2097
2136
  const pollCache = getPollCache(clients);
2098
2137
  const shareEnabled = projectConfig?.profile?.imessageSynced === true;
2099
2138
  const recover = getCloudRecover(clients);
2139
+ const includeGroupEvents = !isSharedMode(clients);
2100
2140
  return mergeStreams(clients.map((entry) => {
2101
2141
  const tracker = shareEnabled ? getContactShareTracker(entry.client) : void 0;
2102
- return clientStream(entry.client, pollCache, entry.phone, tracker ? (chatGuid) => tracker.maybeShare(chatGuid) : void 0, recover);
2142
+ return clientStream(entry.client, pollCache, entry.phone, includeGroupEvents, tracker ? (chatGuid) => tracker.maybeShare(chatGuid) : void 0, recover);
2103
2143
  }));
2104
2144
  };
2105
2145
  //#endregion
@@ -2163,6 +2203,7 @@ const stopTyping$1 = async (remote, spaceId) => {
2163
2203
  const messages = (clients, projectConfig) => messages$1(clients, projectConfig);
2164
2204
  const setBackground = async (remote, spaceId, content) => setBackground$1(remote, spaceId, content);
2165
2205
  const sendCustomizedMiniApp = async (remote, spaceId, content) => sendCustomizedMiniApp$1(remote, spaceId, content);
2206
+ const updateCustomizedMiniApp = async (remote, spaceId, session, content) => updateCustomizedMiniApp$1(remote, spaceId, session, content);
2166
2207
  const setDisplayName = async (remote, spaceId, content) => setDisplayName$1(remote, spaceId, content);
2167
2208
  const getDisplayName = async (remote, spaceId) => getDisplayName$1(remote, spaceId);
2168
2209
  const addParticipants = async (remote, spaceId, content) => addParticipants$1(remote, spaceId, content);
@@ -2208,39 +2249,16 @@ const SPECTRUM_MINI_APP = {
2208
2249
  };
2209
2250
  /**
2210
2251
  * Build the iMessage mini-app card for an `app` content: Spectrum's fixed
2211
- * identity plus the per-message `url` and the `layout` already derived from the
2212
- * URL's link metadata.
2252
+ * identity plus the per-message `url`, optional live-rendering hint, and the
2253
+ * `layout` already derived from the URL's link metadata.
2213
2254
  */
2214
- const toSpectrumMiniApp = (url, layout) => asCustomizedMiniApp({
2255
+ const toSpectrumMiniApp = (url, layout, live) => asCustomizedMiniApp({
2215
2256
  ...SPECTRUM_MINI_APP,
2216
2257
  url,
2217
- layout
2258
+ layout,
2259
+ ...live === void 0 ? {} : { live }
2218
2260
  });
2219
2261
  //#endregion
2220
- //#region src/remote/client.ts
2221
- const isSharedMode = (clients) => clients.length === 1 && clients[0]?.phone === "shared";
2222
- const availablePhones = (clients) => clients.map((c) => c.phone);
2223
- const clientForPhone = (clients, phone) => {
2224
- if (isSharedMode(clients)) {
2225
- const entry = clients[0];
2226
- if (!entry) throw new Error("No iMessage clients configured");
2227
- return entry.client;
2228
- }
2229
- const entry = clients.find((c) => c.phone === phone);
2230
- if (!entry) {
2231
- const list = availablePhones(clients).join(", ") || "<none>";
2232
- throw new Error(`No iMessage client serves phone ${phone}. Available: ${list}`);
2233
- }
2234
- return entry.client;
2235
- };
2236
- const randomPhone = (clients) => {
2237
- if (clients.length === 0) throw new Error("No iMessage phones configured for this account");
2238
- if (isSharedMode(clients)) return SHARED_PHONE;
2239
- const entry = clients[Math.floor(Math.random() * clients.length)];
2240
- if (!entry) throw new Error("No iMessage phones configured for this account");
2241
- return entry.phone;
2242
- };
2243
- //#endregion
2244
2262
  //#region src/index.ts
2245
2263
  const isPollContent = (content) => content.type === "poll" || content.type === "poll_option";
2246
2264
  const cacheRemoteOutbound = (remote, space, record) => {
@@ -2259,6 +2277,25 @@ const cacheRemoteOutbound = (remote, space, record) => {
2259
2277
  };
2260
2278
  const handleEdit = async (client, space, content) => {
2261
2279
  if (isLocal(client)) throw UnsupportedError.action("edit", "iMessage (local mode)");
2280
+ const miniAppCardSession = content.target.miniAppCardSession;
2281
+ const updateMiniAppCardSession = (record) => {
2282
+ const nextSession = record?.miniAppCardSession;
2283
+ if (nextSession) content.target.miniAppCardSession = nextSession;
2284
+ };
2285
+ if (content.content.type === "app") {
2286
+ if (!miniAppCardSession) throw UnsupportedError.content("edit", "iMessage", "mini app card edits require a miniAppCardSession from the original send");
2287
+ const url = await content.content.url();
2288
+ const layout = await content.content.layout();
2289
+ const remote = clientForPhone(client, space.phone);
2290
+ updateMiniAppCardSession(cacheRemoteOutbound(remote, space, await updateCustomizedMiniApp(remote, space.id, miniAppCardSession, toSpectrumMiniApp(url, layout, content.content.live))));
2291
+ return;
2292
+ }
2293
+ if (isCustomizedMiniApp(content.content)) {
2294
+ if (!miniAppCardSession) throw UnsupportedError.content("edit", "iMessage", "customized mini app card edits require a miniAppCardSession from the original send");
2295
+ const remote = clientForPhone(client, space.phone);
2296
+ updateMiniAppCardSession(cacheRemoteOutbound(remote, space, await updateCustomizedMiniApp(remote, space.id, miniAppCardSession, content.content)));
2297
+ return;
2298
+ }
2262
2299
  if (content.content.type !== "text") throw UnsupportedError.content("edit", "iMessage", `only text content can be edited (got "${content.content.type}")`);
2263
2300
  await editMessage(clientForPhone(client, space.phone), space.id, content.target.id, content.content);
2264
2301
  };
@@ -2298,7 +2335,7 @@ const handleApp = async (client, space, content) => {
2298
2335
  if (isLocal(client)) return await send$2(client, space.id, await text(url).build());
2299
2336
  const layout = await content.layout();
2300
2337
  const remote = clientForPhone(client, space.phone);
2301
- return cacheRemoteOutbound(remote, space, await sendCustomizedMiniApp(remote, space.id, toSpectrumMiniApp(url, layout)));
2338
+ return cacheRemoteOutbound(remote, space, await sendCustomizedMiniApp(remote, space.id, toSpectrumMiniApp(url, layout, content.live)));
2302
2339
  };
2303
2340
  const handleRead = async (client, space) => {
2304
2341
  if (isLocal(client)) throw UnsupportedError.action("read", "iMessage (local mode)", "marking chats as read requires remote iMessage");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spectrum-ts/imessage",
3
- "version": "9.1.0",
3
+ "version": "9.3.1",
4
4
  "description": "iMessage provider for spectrum-ts — local and remote (advanced) modes.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,7 +31,7 @@
31
31
  "label": "iMessage"
32
32
  },
33
33
  "dependencies": {
34
- "@photon-ai/advanced-imessage": "^0.12.0",
34
+ "@photon-ai/advanced-imessage": "^1.0.0",
35
35
  "@photon-ai/imessage-kit": "^3.0.0",
36
36
  "@photon-ai/otel": "^3.1.0",
37
37
  "lru-cache": "^11.0.0",