@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 +26 -1
- package/dist/index.js +81 -44
- package/package.json +2 -2
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
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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) =>
|
|
2092
|
-
messageStream(client, phone, onInbound, recover),
|
|
2093
|
-
|
|
2094
|
-
|
|
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
|
|
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
|
|
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.
|
|
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",
|