Skip to content

Commit 5db3136

Browse files
committed
fix advanced-imsg-ts docs
1 parent dc184cc commit 5db3136

6 files changed

Lines changed: 217 additions & 103 deletions

File tree

advanced-kits/imessage/attachments.mdx

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: "Attachments"
33
description: "Upload, download, and stream file attachments"
44
---
55

6-
`im.attachments` handles file attachments - uploading before sending, streaming downloads, forcing iCloud downloads, and extracting Live Photos.
6+
`im.attachments` handles file attachments - uploading before sending, streaming downloads, transparent iCloud recovery, and extracting Live Photos.
77

88
## Supported file types
99

@@ -23,24 +23,41 @@ description: "Upload, download, and stream file attachments"
2323

2424
## Upload an attachment
2525

26-
Upload a file and get back an `AttachmentGuid` to pass to `im.messages.send()`.
26+
Upload a file and get back an `AttachmentInfo` object. Use the `guid` property to pass to `im.messages.send()`.
2727

2828
```ts
29-
const guid = await im.attachments.upload({
29+
const info = await im.attachments.upload({
3030
path: "/path/to/photo.jpg",
31-
name: "photo.jpg", // optional display name
32-
mimeType: "image/jpeg", // optional override
31+
fileName: "photo.jpg", // optional display name
32+
mimeType: "image/jpeg", // optional override
3333
});
3434

35-
await im.messages.send(chat, "", { attachment: guid });
35+
await im.messages.send(chat, "", { attachment: info.guid });
3636
```
3737

38+
`AttachmentInput` is a union type with two variants - file path or raw bytes:
39+
3840
```ts
39-
interface AttachmentInput {
40-
path: string;
41-
name?: string;
42-
mimeType?: string;
43-
}
41+
type AttachmentInput =
42+
| { data: Uint8Array; fileName: string; mimeType: string }
43+
| { path: string; fileName?: string; mimeType?: string };
44+
```
45+
46+
## Upload a Live Photo
47+
48+
Upload a paired HEIC image + MOV video as a Live Photo:
49+
50+
```ts
51+
const info = await im.attachments.uploadLivePhoto({
52+
image: {
53+
data: imageBytes, // Uint8Array
54+
fileName: "photo.heic",
55+
mimeType: "image/heic",
56+
},
57+
video: {
58+
data: videoBytes, // Uint8Array
59+
},
60+
});
4461
```
4562

4663
## Get attachment metadata
@@ -52,22 +69,34 @@ const info = await im.attachments.get(attachmentGuid);
5269
```ts
5370
interface AttachmentInfo {
5471
guid: AttachmentGuid;
55-
originalGuid: string;
56-
mimeType?: string;
57-
fileName?: string;
72+
originalGuid?: AttachmentGuid;
73+
mimeType: string;
74+
fileName: string;
5875
totalBytes: number;
5976
transferState: TransferState;
60-
transferName?: string;
77+
hasLivePhoto: boolean;
78+
height?: number;
79+
hideAttachment: boolean;
80+
isOutgoing: boolean;
81+
isSticker: boolean;
82+
uti: string;
83+
width?: number;
6184
_raw?: unknown;
6285
}
6386
```
6487

88+
## Count attachments
89+
90+
```ts
91+
const total = await im.attachments.count();
92+
```
93+
6594
## Download an attachment
6695

67-
`download()` returns a `StreamedDownload` - both a streaming and a buffered consumption path.
96+
`download()` returns a `StreamedDownload` synchronously - both a streaming and a buffered consumption path.
6897

6998
```ts
70-
const dl = await im.attachments.download(attachmentGuid);
99+
const dl = im.attachments.download(attachmentGuid);
71100

72101
dl.totalBytes; // file size, known from the first chunk
73102

@@ -77,33 +106,33 @@ for await (const chunk of dl.stream) { // ReadableStream<Uint8Array>
77106
}
78107

79108
// Or buffer the whole thing
80-
const buffer = await dl.arrayBuffer();
109+
const buffer = await dl.arrayBuffer(); // Uint8Array
81110
```
82111

83112
```ts
84113
interface StreamedDownload {
85114
totalBytes: number;
86115
stream: ReadableStream<Uint8Array>;
87-
arrayBuffer(): Promise<ArrayBuffer>;
116+
arrayBuffer(): Promise<Uint8Array>;
88117
}
89118
```
90119

91-
## Force download from iCloud
120+
### Quick buffer download
92121

93-
Some attachments are stored in iCloud and not yet on device. Force-download them:
122+
A convenience method that combines `download()` and `arrayBuffer()`:
94123

95124
```ts
96-
await im.attachments.forceDownload(attachmentGuid);
125+
const bytes = await im.attachments.downloadBuffer(attachmentGuid);
126+
// bytes: Uint8Array
97127
```
98128

99129
## Live Photos
100130

101131
Extract the video component from a Live Photo attachment:
102132

103133
```ts
104-
const video = await im.attachments.getLivePhotoVideo({
105-
attachmentGuid,
106-
outputPath: "/tmp/live.mov", // optional - where to write the video
107-
});
108-
// video: LivePhotoInput
134+
const videoDl = im.attachments.getLivePhoto(attachmentGuid);
135+
const videoBytes = await videoDl.arrayBuffer();
109136
```
137+
138+
`getLivePhoto()` returns a `StreamedDownload` synchronously, just like `download()`.

advanced-kits/imessage/chats.mdx

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ const { chat, sendReceipt } = await im.chats.create(["+1234567890"], {
2121

2222
`sendReceipt` is present only when an initial `message` was provided.
2323

24+
**`CreateChatOptions`**
25+
26+
| Option | Type | Description |
27+
|---|---|---|
28+
| `message` | `string` | Initial message text to send |
29+
| `service` | `"iMessage" \| "SMS" \| "RCS"` | Service to use |
30+
| `subject` | `string` | Subject line |
31+
| `effectId` | `string` | Send effect ID |
32+
| `clientMessageId` | `string` | Idempotency key |
33+
2434
### Get a chat
2535

2636
```ts
@@ -31,13 +41,18 @@ The returned `Chat` object contains:
3141

3242
```ts
3343
interface Chat {
34-
guid: ChatGuid;
35-
displayName?: string;
36-
participants: string[];
37-
lastMessage?: Message;
38-
isArchived: boolean;
39-
serviceType: ChatServiceType;
40-
_raw?: unknown;
44+
readonly guid: ChatGuid;
45+
readonly chatIdentifier?: string;
46+
readonly displayName?: string;
47+
readonly groupId?: string;
48+
readonly participants: readonly AddressInfo[];
49+
readonly lastMessage?: Message;
50+
readonly isArchived: boolean;
51+
readonly isFiltered: boolean;
52+
readonly isGroup: boolean;
53+
readonly service: ChatServiceType;
54+
readonly unreadCount?: number;
55+
readonly _raw?: unknown;
4156
}
4257
```
4358

@@ -52,37 +67,35 @@ const totalWithArchived = await im.chats.count({ includeArchived: true });
5267

5368
```ts
5469
// Show typing bubble
55-
await im.chats.setTyping(chatGuid, true);
70+
await im.chats.startTyping(chatGuid);
5671

5772
// Stop typing bubble
58-
await im.chats.setTyping(chatGuid, false);
73+
await im.chats.stopTyping(chatGuid);
5974
```
6075

6176
### Share contact info
6277

6378
```ts
64-
const info = await im.chats.shareContactInfo(chatGuid);
65-
// info: AddressInfo
79+
await im.chats.shareContactInfo(chatGuid);
6680
```
6781

68-
### Add & remove participants
82+
### Get participants
6983

7084
```ts
71-
await im.chats.addParticipant(chatGuid, "+19998887777");
72-
await im.chats.removeParticipant(chatGuid, "+19998887777");
85+
const participants = await im.chats.getParticipants(chatGuid);
86+
// participants: AddressInfo[]
7387
```
7488

75-
### Mark as read
89+
### Leave a chat
7690

7791
```ts
78-
await im.chats.markRead(chatGuid);
92+
await im.chats.leave(chatGuid);
7993
```
8094

81-
### Archive & unarchive
95+
### Mark as read
8296

8397
```ts
84-
await im.chats.setArchived(chatGuid, true);
85-
await im.chats.setArchived(chatGuid, false);
98+
await im.chats.markRead(chatGuid);
8699
```
87100

88101
### Real-time chat events
@@ -113,21 +126,25 @@ for await (const event of im.chats.subscribe("chat.typingIndicator")) {
113126
### Rename a group
114127

115128
```ts
116-
await im.groups.rename(chatGuid, "New Group Name");
129+
const updatedChat = await im.groups.setDisplayName(chatGuid, "New Group Name");
117130
```
118131

119132
### Manage participants
120133

121134
```ts
122-
await im.groups.addParticipant(chatGuid, "newperson@icloud.com");
123-
await im.groups.removeParticipant(chatGuid, "oldperson@icloud.com");
135+
const updatedChat = await im.groups.addParticipant(chatGuid, "newperson@icloud.com");
136+
const updatedChat2 = await im.groups.removeParticipant(chatGuid, "oldperson@icloud.com");
124137
```
125138

126139
### Group icons
127140

128141
```ts
129-
// Change icon
130-
await im.groups.setIcon(chatGuid, attachmentGuid);
142+
// Get icon
143+
const iconBytes = await im.groups.getIcon(chatGuid);
144+
// iconBytes: Uint8Array | null
145+
146+
// Change icon (pass raw image bytes)
147+
await im.groups.setIcon(chatGuid, imageBytes); // imageBytes: Uint8Array
131148

132149
// Remove icon
133150
await im.groups.removeIcon(chatGuid);
@@ -136,7 +153,15 @@ await im.groups.removeIcon(chatGuid);
136153
### Group backgrounds
137154

138155
```ts
139-
await im.groups.setBackground(chatGuid, { imagePath: "/path/to/image.jpg" });
156+
// Get background
157+
const bgInfo = await im.groups.getBackground(chatGuid);
158+
// bgInfo: BackgroundInfo | null
159+
160+
// Set background (pass raw image bytes)
161+
const bgInfo2 = await im.groups.setBackground(chatGuid, imageBytes); // imageBytes: Uint8Array
162+
// bgInfo2: BackgroundInfo { channelGuid?, imageUrl?, backgroundId? }
163+
164+
// Remove background
140165
await im.groups.removeBackground(chatGuid);
141166
```
142167

advanced-kits/imessage/error-handling.mdx

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ IMessageError
1616
└── ConnectionError network or gRPC transport failure
1717
```
1818

19+
**gRPC status code mapping**
20+
21+
| gRPC status | SDK error class |
22+
|---|---|
23+
| `UNAUTHENTICATED`, `PERMISSION_DENIED` | `AuthenticationError` |
24+
| `NOT_FOUND` | `NotFoundError` |
25+
| `RESOURCE_EXHAUSTED` | `RateLimitError` |
26+
| `INVALID_ARGUMENT`, `FAILED_PRECONDITION` | `ValidationError` |
27+
| `UNAVAILABLE`, `DEADLINE_EXCEEDED` | `ConnectionError` |
28+
| All others | `IMessageError` (base class) |
29+
1930
## Basic usage
2031

2132
```ts
@@ -57,13 +68,35 @@ All error subclasses inherit:
5768

5869
```ts
5970
class IMessageError extends Error {
60-
readonly code: ErrorCode; // SDK-level error code enum
61-
readonly retryable: boolean; // whether retrying the request may succeed
62-
readonly grpcCode: number; // underlying gRPC status code
71+
readonly code: ErrorCode; // SDK-level error code enum
72+
readonly retryable: boolean; // whether retrying the request may succeed
73+
readonly grpcCode: number; // underlying gRPC status code
74+
readonly context: Record<string, string>; // extra debug info from gRPC trailing metadata
6375
}
6476
```
6577

66-
`retryable` is `true` for `RateLimitError` and `ConnectionError`, `false` for all others.
78+
The `retryable` flag is read from the server's gRPC trailing metadata (`x-retryable` header), so it is determined by the server on a per-response basis. By convention, `RateLimitError` and `ConnectionError` are typically retryable, but the server may override this for any error type.
79+
80+
## Error codes
81+
82+
`ErrorCode` has 22 values across 6 categories:
83+
84+
| Category | Error codes |
85+
|---|---|
86+
| Authentication | `unauthenticated`, `tokenExpired`, `tokenBlocked`, `unauthorized` |
87+
| Rate limiting | `dailyLimitExceeded`, `recipientLimitExceeded` |
88+
| Deduplication | `duplicateMessage` |
89+
| Not found | `chatNotFound`, `messageNotFound`, `attachmentNotFound`, `addressNotFound`, `scheduledMessageNotFound`, `pollNotFound` |
90+
| Validation | `invalidArgument`, `preconditionFailed`, `operationNotSupported`, `privateApiUnavailable` |
91+
| Infrastructure | `serviceUnavailable`, `timeout`, `internalError`, `databaseError`, `networkError` |
92+
93+
```ts
94+
import { ErrorCode } from "@photon-ai/advanced-imessage";
95+
96+
if (err instanceof IMessageError && err.code === ErrorCode.chatNotFound) {
97+
// handle specifically
98+
}
99+
```
67100

68101
## Retrying with the SDK
69102

@@ -75,8 +108,8 @@ const im = createClient({
75108
token: "...",
76109
retry: {
77110
maxAttempts: 3,
78-
initialDelayMs: 200,
79-
maxDelayMs: 2000,
111+
initialDelay: 200,
112+
maxDelay: 5000,
80113
},
81114
});
82115
```
@@ -85,14 +118,14 @@ When `retry` is enabled, `ConnectionError` and `RateLimitError` are retried tran
85118

86119
## Escape hatch: `_raw`
87120

88-
If the error exposes insufficient detail, every domain type (including errors) carries `_raw` - the raw gRPC response - for debugging:
121+
Most domain types carry `_raw` containing the raw gRPC response for debugging purposes. The types that include `_raw` are `Chat`, `Message`, `AttachmentInfo`, and `AddressInfo`. Other types such as `ScheduledMessage`, `FindMyFriend`, and `PollInfo` do not include `_raw`.
89122

90123
```ts
91124
try {
92125
await im.messages.send(chat, "Hello!");
93126
} catch (err) {
94127
if (err instanceof IMessageError) {
95-
console.log(err.code, err.grpcCode, err.retryable);
128+
console.log(err.code, err.grpcCode, err.retryable, err.context);
96129
}
97130
}
98131
```

advanced-kits/imessage/getting-started.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const im = createClient(options: ClientOptions): AdvancedIMessage;
5252

5353
| Option | Type | Description |
5454
|---|---|---|
55-
| `address` | `string` | Host and port of the Photon server |
55+
| `address` | `string` | Host (and optional port) of the Photon server |
5656
| `token` | `string \| () => Promise<string>` | Static token or async token factory |
5757
| `tls` | `boolean` | Enable TLS (default: `false`) |
5858
| `timeout` | `number` | Request timeout in milliseconds |

0 commit comments

Comments
 (0)