@spectrum-ts/imessage 5.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License Copyright (c) 2025 Photon AI
2
+
3
+ Permission is hereby granted,
4
+ free of charge, to any person obtaining a copy of this software and associated
5
+ documentation files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use, copy, modify, merge,
7
+ publish, distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice
12
+ (including the next paragraph) shall be included in all copies or substantial
13
+ portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
18
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @spectrum-ts/imessage
2
+
3
+ iMessage provider for [spectrum-ts](https://github.com/photon-hq/spectrum-ts), supporting local (imessage-kit) and remote (advanced-imessage) modes — including tapbacks, special effects, polls, and mini-apps.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ bun add spectrum-ts @spectrum-ts/imessage
9
+ ```
10
+
11
+ ## Use
12
+
13
+ ```ts
14
+ import { Spectrum } from "spectrum-ts";
15
+ import { imessage } from "@spectrum-ts/imessage";
16
+
17
+ const spectrum = Spectrum({
18
+ providers: [imessage.config({ /* ... */ })],
19
+ });
20
+ ```
21
+
22
+ This package also exports the iMessage-specific content helpers `effect`, `read`, `background`, `customizedMiniApp`, and `nativeContactCard`.
23
+
24
+ `nativeContactCard()` shares the bot account's own contact card (Apple's "Share Name and Photo") with a chat — remote mode only:
25
+
26
+ ```ts
27
+ import { nativeContactCard } from "@spectrum-ts/imessage";
28
+
29
+ await space.send(nativeContactCard());
30
+ // or the sugar form, typed on the iMessage space:
31
+ await space.shareContactCard();
32
+ ```
33
+
34
+ See the [spectrum-ts documentation](https://photon.codes/spectrum) for the full guide.
@@ -0,0 +1,269 @@
1
+ import { AdvancedIMessage, MessageEffect } from "@photon-ai/advanced-imessage";
2
+ import { IMessageSDK } from "@photon-ai/imessage-kit";
3
+ import { Attachment, ContentBuilder, ContentInput, SchemaMessage, Space, read } from "@spectrum-ts/core";
4
+ import { PhotoInput } from "@spectrum-ts/core/authoring";
5
+ import z from "zod";
6
+
7
+ //#region src/content/background.d.ts
8
+ type BackgroundInput = PhotoInput;
9
+ /**
10
+ * Set or clear the chat background. iMessage-only, remote-only.
11
+ *
12
+ * - `background("clear")` — remove the current chat background.
13
+ * - `background("./photo.jpg")` — set background from a filesystem path.
14
+ * MIME type is inferred from the extension; override with `options.mimeType`.
15
+ * - `background(new URL("https://…/photo.jpg"))` — fetch the background
16
+ * lazily over the network. Bytes stay in memory (safe in read-only
17
+ * environments). MIME type is inferred from the URL pathname extension;
18
+ * override with `options.mimeType` when the URL has no usable extension.
19
+ * - `background(buffer, { mimeType })` — set background from in-memory bytes.
20
+ * `options.mimeType` is required.
21
+ *
22
+ * `"clear"` is a reserved string-literal sentinel. If you have a file literally
23
+ * named `clear` with no extension, pass `"./clear"` or load it as a Buffer.
24
+ *
25
+ * `space.send(background(...))` is the canonical form; `space.background(...)`
26
+ * is sugar attached via `PlatformDef.space.actions` (only typed on
27
+ * `PlatformSpace<IMessageDef>`).
28
+ *
29
+ * `Background` is intentionally not a member of the universal `Content`
30
+ * union — the `as unknown as Content` cast keeps the builder shape compatible
31
+ * with the framework's `ContentBuilder.build(): Promise<Content>` signature.
32
+ * The framework treats it as a fire-and-forget control signal at runtime.
33
+ */
34
+ declare function background(input: "clear"): ContentBuilder;
35
+ declare function background(input: string | Buffer | URL, options?: {
36
+ mimeType?: string;
37
+ }): ContentBuilder;
38
+ //#endregion
39
+ //#region src/content/contact-card.d.ts
40
+ /**
41
+ * iMessage-only "share contact card" control signal. Pushes the *local
42
+ * account's native contact card* (the name + photo a recipient sees in their
43
+ * Messages app) to a chat via the SDK's `chats.shareContactInfo`.
44
+ *
45
+ * This is Apple's "Share Name and Photo" mechanism — distinct from the
46
+ * universal `contact(...)` content, which uploads an arbitrary person's vCard
47
+ * as a *file* attachment. There is no payload: the card shared is always the
48
+ * bot account's own.
49
+ *
50
+ * Like `background`, it lives entirely under the iMessage provider and never
51
+ * enters the universal `Content` discriminated union. The framework recognizes
52
+ * it via two generic content-level contracts:
53
+ *
54
+ * 1. `__platform: "iMessage"` — `findUnsupportedPlatformContent` in
55
+ * `platform/build.ts` reads this tag and warns-and-skips when a different
56
+ * platform receives it.
57
+ * 2. `__fireAndForget: true` — `dispatchSend`'s fire-and-forget check treats
58
+ * this as a side-effecting send that returns no message id, the same way it
59
+ * treats `read` / `typing`.
60
+ *
61
+ * iMessage's `send` handler narrows back via the `isContactCard` type guard
62
+ * before dispatching to `chats.shareContactInfo`.
63
+ */
64
+ declare const contactCardSchema: z.ZodObject<{
65
+ type: z.ZodLiteral<"contactCard">;
66
+ __platform: z.ZodLiteral<"iMessage">;
67
+ __fireAndForget: z.ZodLiteral<true>;
68
+ }, z.core.$strip>;
69
+ type ContactCard = z.infer<typeof contactCardSchema>;
70
+ /**
71
+ * Share the bot account's native iMessage contact card (name + photo) with the
72
+ * chat. iMessage-only, remote-only.
73
+ *
74
+ * `space.send(nativeContactCard())` is the canonical form; `space.shareContactCard()`
75
+ * is sugar attached via `PlatformDef.space.actions` (only typed on
76
+ * `PlatformSpace<IMessageDef>`).
77
+ *
78
+ * This is an explicit, on-demand share and always fires — unlike the automatic
79
+ * best-effort share gated behind the `imessageSynced` project profile, which
80
+ * dedupes to once per chat per 24h (see `remote/contact-share.ts`). Works in
81
+ * both DMs and group chats; the recipient chooses whether to accept the card.
82
+ *
83
+ * `ContactCard` is intentionally not a member of the universal `Content`
84
+ * union — the `as unknown as Content` cast keeps the builder shape compatible
85
+ * with the framework's `ContentBuilder.build(): Promise<Content>` signature.
86
+ * The framework treats it as a fire-and-forget control signal at runtime.
87
+ */
88
+ declare function nativeContactCard(): ContentBuilder;
89
+ //#endregion
90
+ //#region src/content/customized-mini-app.d.ts
91
+ declare const layoutSchema: z.ZodObject<{
92
+ caption: z.ZodOptional<z.ZodString>;
93
+ subcaption: z.ZodOptional<z.ZodString>;
94
+ trailingCaption: z.ZodOptional<z.ZodString>;
95
+ trailingSubcaption: z.ZodOptional<z.ZodString>;
96
+ image: z.ZodOptional<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
97
+ imageTitle: z.ZodOptional<z.ZodString>;
98
+ imageSubtitle: z.ZodOptional<z.ZodString>;
99
+ summary: z.ZodOptional<z.ZodString>;
100
+ }, z.core.$strip>;
101
+ /**
102
+ * iMessage-only mini-app card content. Lives entirely under the iMessage
103
+ * provider — never enters the universal `Content` discriminated union. The
104
+ * framework recognizes it via the generic content-level platform contract:
105
+ *
106
+ * - `__platform: "iMessage"` — `findUnsupportedPlatformContent` reads this tag
107
+ * and warns-and-skips when a different platform receives it.
108
+ *
109
+ * Unlike `background` / `read`, this content is **not** `__fireAndForget`: it
110
+ * produces a real outbound message, so the iMessage `send` handler narrows
111
+ * back to `CustomizedMiniApp` via the `isCustomizedMiniApp` guard and returns
112
+ * the resulting `ProviderMessageRecord` (rather than `void`).
113
+ */
114
+ declare const customizedMiniAppSchema: z.ZodObject<{
115
+ type: z.ZodLiteral<"customized-mini-app">;
116
+ __platform: z.ZodLiteral<"iMessage">;
117
+ appName: z.ZodString;
118
+ appStoreId: z.ZodOptional<z.ZodNumber>;
119
+ extensionBundleId: z.ZodString;
120
+ layout: z.ZodObject<{
121
+ caption: z.ZodOptional<z.ZodString>;
122
+ subcaption: z.ZodOptional<z.ZodString>;
123
+ trailingCaption: z.ZodOptional<z.ZodString>;
124
+ trailingSubcaption: z.ZodOptional<z.ZodString>;
125
+ image: z.ZodOptional<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>>;
126
+ imageTitle: z.ZodOptional<z.ZodString>;
127
+ imageSubtitle: z.ZodOptional<z.ZodString>;
128
+ summary: z.ZodOptional<z.ZodString>;
129
+ }, z.core.$strip>;
130
+ teamId: z.ZodString;
131
+ url: z.ZodURL;
132
+ }, z.core.$strip>;
133
+ type CustomizedMiniApp = z.infer<typeof customizedMiniAppSchema>;
134
+ type CustomizedMiniAppLayout = z.infer<typeof layoutSchema>;
135
+ type CustomizedMiniAppInput = Omit<CustomizedMiniApp, "type" | "__platform">;
136
+ /**
137
+ * Construct a `customized-mini-app` content value. iMessage-only, remote-only.
138
+ *
139
+ * The layout is what recipients see in the bubble. `teamId` and
140
+ * `extensionBundleId` identify the iMessage extension that receives `url` when
141
+ * the recipient taps the card; the server constructs the matching
142
+ * `MSMessageExtensionBalloonPlugin` plugin id from these values. `appStoreId`
143
+ * is optional and only points recipients without the extension at its App
144
+ * Store entry.
145
+ *
146
+ * `space.send(customizedMiniApp(...))` is the canonical form.
147
+ *
148
+ * `CustomizedMiniApp` is intentionally not a member of the universal `Content`
149
+ * union — the `as unknown as Content` cast keeps the builder shape compatible
150
+ * with the framework's `ContentBuilder.build(): Promise<Content>` signature.
151
+ */
152
+ declare function customizedMiniApp(input: CustomizedMiniAppInput): ContentBuilder;
153
+ //#endregion
154
+ //#region src/content/effect.d.ts
155
+ type IMessageMessageEffect = MessageEffect;
156
+ declare function effect(input: ContentInput, messageEffect: IMessageMessageEffect): ContentBuilder;
157
+ //#endregion
158
+ //#region src/types.d.ts
159
+ interface RemoteClient {
160
+ client: AdvancedIMessage;
161
+ phone: string;
162
+ }
163
+ type IMessageClient = IMessageSDK | RemoteClient[];
164
+ declare const userSchema: z.ZodObject<{}, z.core.$strip>;
165
+ declare const spaceSchema: z.ZodObject<{
166
+ id: z.ZodString;
167
+ type: z.ZodEnum<{
168
+ dm: "dm";
169
+ group: "group";
170
+ }>;
171
+ phone: z.ZodString;
172
+ }, z.core.$strip>;
173
+ type IMessageMessage = SchemaMessage<typeof userSchema, typeof spaceSchema> & {
174
+ direction?: "inbound" | "outbound";
175
+ partIndex?: number;
176
+ parentId?: string;
177
+ };
178
+ //#endregion
179
+ //#region src/index.d.ts
180
+ declare const imessage: import("@spectrum-ts/core").Platform<import("@spectrum-ts/core").PlatformDef<"iMessage", import("zod").ZodUnion<readonly [import("zod").ZodObject<{
181
+ local: import("zod").ZodLiteral<true>;
182
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
183
+ local: import("zod").ZodDefault<import("zod").ZodOptional<import("zod").ZodLiteral<false>>>;
184
+ clients: import("zod").ZodOptional<import("zod").ZodUnion<[import("zod").ZodObject<{
185
+ address: import("zod").ZodString;
186
+ token: import("zod").ZodString;
187
+ phone: import("zod").ZodString;
188
+ }, import("zod/v4/core").$strip>, import("zod").ZodArray<import("zod").ZodObject<{
189
+ address: import("zod").ZodString;
190
+ token: import("zod").ZodString;
191
+ phone: import("zod").ZodString;
192
+ }, import("zod/v4/core").$strip>>]>>;
193
+ }, import("zod/v4/core").$strip>]>, import("zod").ZodType<object, unknown, import("zod/v4/core").$ZodTypeInternals<object, unknown>> | undefined, import("zod").ZodObject<{
194
+ id: import("zod").ZodString;
195
+ type: import("zod").ZodEnum<{
196
+ dm: "dm";
197
+ group: "group";
198
+ }>;
199
+ phone: import("zod").ZodString;
200
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
201
+ phone: import("zod").ZodOptional<import("zod").ZodString>;
202
+ }, import("zod/v4/core").$strip>, IMessageClient, {
203
+ id: string;
204
+ }, {
205
+ id: string;
206
+ type: "dm" | "group";
207
+ phone: string;
208
+ }, import("zod").ZodObject<{
209
+ partIndex: import("zod").ZodOptional<import("zod").ZodNumber>;
210
+ parentId: import("zod").ZodOptional<import("zod").ZodString>;
211
+ }, import("zod/v4/core").$strip>, IMessageMessage, undefined, {
212
+ background: (space: Space, input: BackgroundInput, opts?: {
213
+ mimeType?: string;
214
+ }) => Promise<void>;
215
+ shareContactCard: (space: Space) => Promise<void>;
216
+ }, Record<never, never>, {
217
+ getMessage: ({
218
+ client
219
+ }: {
220
+ client: IMessageClient;
221
+ config: {
222
+ local: true;
223
+ } | {
224
+ local: false;
225
+ clients?: {
226
+ address: string;
227
+ token: string;
228
+ phone: string;
229
+ } | {
230
+ address: string;
231
+ token: string;
232
+ phone: string;
233
+ }[] | undefined;
234
+ };
235
+ store: import("@spectrum-ts/core").Store;
236
+ }, space: {
237
+ id: string;
238
+ type: "dm" | "group";
239
+ phone: string;
240
+ } & {
241
+ id: string;
242
+ __platform: string;
243
+ }, messageId: string) => Promise<IMessageMessage | undefined>;
244
+ getAttachment: ({
245
+ client
246
+ }: {
247
+ client: IMessageClient;
248
+ }, guid: string, phone?: string) => Promise<Attachment | undefined>;
249
+ }>> & Readonly<{
250
+ effect: {
251
+ message: {
252
+ readonly slam: "com.apple.MobileSMS.expressivesend.impact";
253
+ readonly loud: "com.apple.MobileSMS.expressivesend.loud";
254
+ readonly gentle: "com.apple.MobileSMS.expressivesend.gentle";
255
+ readonly invisible: "com.apple.MobileSMS.expressivesend.invisibleink";
256
+ readonly confetti: "com.apple.messages.effect.CKConfettiEffect";
257
+ readonly fireworks: "com.apple.messages.effect.CKFireworksEffect";
258
+ readonly balloons: "com.apple.messages.effect.CKBalloonEffect";
259
+ readonly heart: "com.apple.messages.effect.CKHeartEffect";
260
+ readonly lasers: "com.apple.messages.effect.CKLasersEffect";
261
+ readonly celebration: "com.apple.messages.effect.CKHappyBirthdayEffect";
262
+ readonly sparkles: "com.apple.messages.effect.CKSparklesEffect";
263
+ readonly spotlight: "com.apple.messages.effect.CKSpotlightEffect";
264
+ readonly echo: "com.apple.messages.effect.CKEchoEffect";
265
+ };
266
+ };
267
+ }>;
268
+ //#endregion
269
+ export { type BackgroundInput, type ContactCard, type CustomizedMiniApp, type CustomizedMiniAppInput, type CustomizedMiniAppLayout, type IMessageMessageEffect, background, customizedMiniApp, effect, imessage, nativeContactCard, read };