@playdrop/playdrop-cli 0.16.12 → 0.16.16

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.
@@ -21,175 +21,299 @@ __export(chat_exports, {
21
21
  });
22
22
  module.exports = __toCommonJS(chat_exports);
23
23
  var import_request = require("../core/request.js");
24
- function pageQuery(options = {}) {
24
+ function chatWebSocketUrl(baseUrl) {
25
+ const url = new URL(baseUrl);
26
+ if (url.protocol === "https:")
27
+ url.protocol = "wss:";
28
+ else if (url.protocol === "http:")
29
+ url.protocol = "ws:";
30
+ else
31
+ throw new Error("chat_realtime_invalid_api_url");
32
+ url.pathname = `${url.pathname.replace(/\/$/, "")}/chat/ws`;
33
+ url.search = "";
34
+ url.hash = "";
35
+ return url.toString();
36
+ }
37
+ function parseRealtimeFrame(value) {
38
+ if (typeof value !== "string")
39
+ throw new Error("chat_realtime_invalid_frame");
40
+ let parsed;
41
+ try {
42
+ parsed = JSON.parse(value);
43
+ } catch {
44
+ throw new Error("chat_realtime_invalid_frame");
45
+ }
46
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
47
+ throw new Error("chat_realtime_invalid_frame");
48
+ const frame = parsed;
49
+ const type = frame["type"];
50
+ if (type === "ready" && typeof frame["heartbeatIntervalMs"] === "number")
51
+ return parsed;
52
+ if ((type === "subscribed" || type === "inbox.updated" || type === "resync_required") && typeof frame["conversationId"] === "string") {
53
+ return parsed;
54
+ }
55
+ if (type === "conversation.event" && frame["event"] && typeof frame["event"] === "object") {
56
+ return parsed;
57
+ }
58
+ if (type === "typing.updated" && typeof frame["conversationId"] === "string" && typeof frame["userId"] === "number" && typeof frame["active"] === "boolean" && typeof frame["expiresAt"] === "string") {
59
+ return parsed;
60
+ }
61
+ if (type === "error" && typeof frame["code"] === "string")
62
+ return parsed;
63
+ throw new Error("chat_realtime_invalid_frame");
64
+ }
65
+ function pageQuery(options = {}, legacy = false) {
25
66
  const params = new URLSearchParams();
26
- if (options.beforeId !== void 0)
27
- params.set("beforeId", String(options.beforeId));
67
+ if (legacy && options.beforeId !== void 0)
68
+ params.set("beforeId", options.beforeId);
69
+ if (!legacy && options.before !== void 0)
70
+ params.set("before", options.before);
71
+ if (!legacy && options.after !== void 0)
72
+ params.set("after", options.after);
73
+ if (!legacy && options.around !== void 0)
74
+ params.set("around", options.around);
28
75
  if (options.limit !== void 0)
29
76
  params.set("limit", String(options.limit));
30
77
  const query = params.toString();
31
78
  return query ? `?${query}` : "";
32
79
  }
33
- function fileName(input) {
34
- const explicit = input.fileName?.trim();
35
- if (explicit)
36
- return explicit;
37
- const named = input.file;
38
- return typeof named.name === "string" && named.name.trim() ? named.name.trim() : "attachment";
39
- }
40
- function needsMultipart(input) {
41
- return Boolean(input.files?.length || input.links?.length);
42
- }
43
- function jsonPayload(input) {
44
- return {
45
- ...input.body !== void 0 ? { body: input.body } : {},
46
- ...input.attachments !== void 0 ? { attachments: input.attachments } : {},
47
- ..."clientKey" in input && input.clientKey !== void 0 ? { clientKey: input.clientKey } : {},
48
- ...input.eventType !== void 0 ? { eventType: input.eventType } : {},
49
- ...input.eventData !== void 0 ? { eventData: input.eventData } : {},
50
- ..."asSystem" in input && input.asSystem !== void 0 ? { asSystem: input.asSystem } : {}
51
- };
80
+ function listQuery(input = {}) {
81
+ const params = new URLSearchParams();
82
+ if (input.type)
83
+ params.set("type", input.type);
84
+ if (input.cursor)
85
+ params.set("cursor", input.cursor);
86
+ if (input.limit !== void 0)
87
+ params.set("limit", String(input.limit));
88
+ if (input.query)
89
+ params.set("query", input.query);
90
+ const query = params.toString();
91
+ return query ? `?${query}` : "";
52
92
  }
53
93
  function buildChatApiClientMethods(input) {
54
94
  const { request, handleApiError, fetchImpl, includeBrowserCredentials, resolveBaseUrl, resolveUrl, parseResponseBody, resolveToken, getClientHeaders } = input;
55
- async function authorizedHeaders() {
95
+ async function authorizedHeaders(contentType) {
56
96
  const headers = new Headers(getClientHeaders());
57
- headers.delete("content-type");
97
+ if (contentType)
98
+ headers.set("content-type", contentType);
99
+ else
100
+ headers.delete("content-type");
58
101
  headers.set("accept", "application/json");
59
102
  const token = await resolveToken();
60
103
  if (token)
61
104
  headers.set("authorization", `Bearer ${token}`);
62
105
  return headers;
63
106
  }
64
- async function multipartWrite(method, path, message) {
65
- const form = new FormData();
66
- form.set("payload", JSON.stringify(jsonPayload(message)));
67
- for (const link of message.links ?? [])
68
- form.append("link", link);
69
- for (const attachment of message.files ?? [])
70
- form.append("file", attachment.file, fileName(attachment));
71
- const response = await fetchImpl(resolveUrl(resolveBaseUrl(), path), {
72
- method,
73
- headers: await authorizedHeaders(),
74
- body: form,
75
- ...(0, import_request.buildBrowserCredentialsInit)(includeBrowserCredentials)
76
- });
77
- const body = await parseResponseBody(response);
78
- const result = { status: response.status, body, headers: response.headers };
79
- if (response.status !== 200 && response.status !== 201)
80
- handleApiError(result, "chat_write_failed");
81
- return body;
82
- }
83
- return {
84
- async fetchChatChannels() {
85
- const response = await request({ method: "GET", path: "/chat/channels" });
107
+ const methods = {
108
+ async fetchChatConversations(options = {}) {
109
+ const response = await request({ method: "GET", path: `/chat/conversations${listQuery(options)}` });
86
110
  if (response.status !== 200)
87
- handleApiError(response, "fetch_chat_channels");
111
+ handleApiError(response, "fetch_chat_conversations");
88
112
  return response.body;
89
113
  },
90
- async fetchChatMessages(channel, options = {}) {
114
+ async fetchChatConversation(conversationId) {
115
+ const response = await request({ method: "GET", path: `/chat/conversations/${encodeURIComponent(conversationId)}` });
116
+ if (response.status !== 200)
117
+ handleApiError(response, "fetch_chat_conversation");
118
+ return response.body;
119
+ },
120
+ async createDirectChatConversation(username) {
121
+ const response = await request({ method: "POST", path: "/chat/direct-conversations", body: { username } });
122
+ if (response.status !== 200 && response.status !== 201)
123
+ handleApiError(response, "create_direct_chat_conversation");
124
+ return response.body;
125
+ },
126
+ async fetchConversationMessages(conversationId, options = {}) {
91
127
  const response = await request({
92
128
  method: "GET",
93
- path: `/chat/channels/${encodeURIComponent(channel)}/messages${pageQuery(options)}`
129
+ path: `/chat/conversations/${encodeURIComponent(conversationId)}/messages${pageQuery(options)}`
94
130
  });
95
131
  if (response.status !== 200)
96
132
  handleApiError(response, "fetch_chat_messages");
97
133
  return response.body;
98
134
  },
99
- async markChatChannelRead(channel, lastReadMessageId) {
135
+ async sendConversationMessage(conversationId, message) {
100
136
  const response = await request({
101
- method: "PUT",
102
- path: `/chat/channels/${encodeURIComponent(channel)}/read`,
103
- body: { lastReadMessageId }
137
+ method: "POST",
138
+ path: `/chat/conversations/${encodeURIComponent(conversationId)}/messages`,
139
+ body: message
104
140
  });
105
- if (response.status !== 200)
106
- handleApiError(response, "mark_chat_channel_read");
141
+ if (response.status !== 201)
142
+ handleApiError(response, "send_chat_message");
107
143
  return response.body;
108
144
  },
109
- async fetchChatThread(messageId, options = {}) {
145
+ async sendSystemChatMessage(channel, message) {
110
146
  const response = await request({
111
- method: "GET",
112
- path: `/chat/messages/${encodeURIComponent(String(messageId))}/replies${pageQuery(options)}`
147
+ method: "POST",
148
+ path: `/admin/chat/channels/${encodeURIComponent(channel)}/system-messages`,
149
+ body: message
113
150
  });
114
- if (response.status !== 200)
115
- handleApiError(response, "fetch_chat_thread");
116
- return response.body;
117
- },
118
- async sendChatMessage(channel, message) {
119
- const path = `/chat/channels/${encodeURIComponent(channel)}/messages`;
120
- if (needsMultipart(message))
121
- return await multipartWrite("POST", path, message);
122
- const response = await request({ method: "POST", path, body: jsonPayload(message) });
123
- if (response.status !== 200 && response.status !== 201)
124
- handleApiError(response, "send_chat_message");
151
+ if (response.status !== 201)
152
+ handleApiError(response, "send_system_chat_message");
125
153
  return response.body;
126
154
  },
127
155
  async replyChatMessage(messageId, message) {
128
- const path = `/chat/messages/${encodeURIComponent(String(messageId))}/replies`;
129
- if (needsMultipart(message))
130
- return await multipartWrite("POST", path, message);
131
- const response = await request({ method: "POST", path, body: jsonPayload(message) });
132
- if (response.status !== 200 && response.status !== 201)
156
+ const response = await request({
157
+ method: "POST",
158
+ path: `/chat/messages/${encodeURIComponent(messageId)}/replies`,
159
+ body: message
160
+ });
161
+ if (response.status !== 201)
133
162
  handleApiError(response, "reply_chat_message");
134
163
  return response.body;
135
164
  },
165
+ async replySystemChatMessage(messageId, message) {
166
+ const response = await request({
167
+ method: "POST",
168
+ path: `/admin/chat/messages/${encodeURIComponent(messageId)}/system-replies`,
169
+ body: message
170
+ });
171
+ if (response.status !== 201)
172
+ handleApiError(response, "reply_system_chat_message");
173
+ return response.body;
174
+ },
136
175
  async editChatMessage(messageId, message) {
137
- const path = `/chat/messages/${encodeURIComponent(String(messageId))}`;
138
- if (needsMultipart(message))
139
- return await multipartWrite("PATCH", path, message);
140
- const response = await request({ method: "PATCH", path, body: jsonPayload(message) });
176
+ const response = await request({ method: "PATCH", path: `/chat/messages/${encodeURIComponent(messageId)}`, body: message });
141
177
  if (response.status !== 200)
142
178
  handleApiError(response, "edit_chat_message");
143
179
  return response.body;
144
180
  },
181
+ async deleteChatMessage(messageId) {
182
+ const response = await request({ method: "DELETE", path: `/chat/messages/${encodeURIComponent(messageId)}` });
183
+ if (response.status !== 200)
184
+ handleApiError(response, "delete_chat_message");
185
+ return response.body;
186
+ },
187
+ async fetchChatThread(messageId, options = {}) {
188
+ const response = await request({ method: "GET", path: `/chat/messages/${encodeURIComponent(messageId)}/thread${pageQuery(options)}` });
189
+ if (response.status !== 200)
190
+ handleApiError(response, "fetch_chat_thread");
191
+ return response.body;
192
+ },
145
193
  async reactChatMessage(messageId, emoji) {
146
- const response = await request({
147
- method: "PUT",
148
- path: `/chat/messages/${encodeURIComponent(String(messageId))}/reactions/${encodeURIComponent(emoji)}`
149
- });
194
+ const response = await request({ method: "PUT", path: `/chat/messages/${encodeURIComponent(messageId)}/reactions/${encodeURIComponent(emoji)}` });
150
195
  if (response.status !== 200)
151
196
  handleApiError(response, "react_chat_message");
152
197
  return response.body;
153
198
  },
154
199
  async unreactChatMessage(messageId, emoji) {
155
- const response = await request({
156
- method: "DELETE",
157
- path: `/chat/messages/${encodeURIComponent(String(messageId))}/reactions/${encodeURIComponent(emoji)}`
158
- });
200
+ const response = await request({ method: "DELETE", path: `/chat/messages/${encodeURIComponent(messageId)}/reactions/${encodeURIComponent(emoji)}` });
159
201
  if (response.status !== 200)
160
202
  handleApiError(response, "unreact_chat_message");
161
203
  return response.body;
162
204
  },
163
- async grantChatAccess(channel, username, canWrite) {
205
+ async markChatConversationRead(conversationId, lastReadMessageId) {
164
206
  const response = await request({
165
207
  method: "PUT",
166
- path: `/admin/chat/channels/${encodeURIComponent(channel)}/access/${encodeURIComponent(username)}`,
167
- body: { canWrite }
208
+ path: `/chat/conversations/${encodeURIComponent(conversationId)}/read`,
209
+ body: { lastReadMessageId }
168
210
  });
169
211
  if (response.status !== 200)
170
- handleApiError(response, "grant_chat_access");
212
+ handleApiError(response, "mark_chat_conversation_read");
171
213
  return response.body;
172
214
  },
173
- async revokeChatAccess(channel, username) {
215
+ async updateChatConversationState(conversationId, state) {
174
216
  const response = await request({
175
- method: "DELETE",
176
- path: `/admin/chat/channels/${encodeURIComponent(channel)}/access/${encodeURIComponent(username)}`
217
+ method: "PATCH",
218
+ path: `/chat/conversations/${encodeURIComponent(conversationId)}/state`,
219
+ body: state
177
220
  });
221
+ if (response.status !== 200)
222
+ handleApiError(response, "update_chat_conversation_state");
223
+ return response.body;
224
+ },
225
+ async reportChatMessage(messageId, body) {
226
+ const response = await request({ method: "POST", path: `/chat/messages/${encodeURIComponent(messageId)}/report`, body });
227
+ if (response.status !== 201)
228
+ handleApiError(response, "report_chat_message");
229
+ return response.body;
230
+ },
231
+ async fetchChatFriends(options = {}) {
232
+ const response = await request({ method: "GET", path: `/me/friends${listQuery(options)}` });
233
+ if (response.status !== 200)
234
+ handleApiError(response, "fetch_chat_friends");
235
+ return response.body;
236
+ },
237
+ async fetchChatNotificationPreferences() {
238
+ const response = await request({ method: "GET", path: "/me/chat-notification-preferences" });
239
+ if (response.status !== 200)
240
+ handleApiError(response, "fetch_chat_notification_preferences");
241
+ return response.body;
242
+ },
243
+ async updateChatNotificationPreferences(preferences) {
244
+ const response = await request({ method: "PATCH", path: "/me/chat-notification-preferences", body: preferences });
245
+ if (response.status !== 200)
246
+ handleApiError(response, "update_chat_notification_preferences");
247
+ return response.body;
248
+ },
249
+ async createChatAttachmentUploadSession(conversationId, file) {
250
+ const response = await request({
251
+ method: "POST",
252
+ path: `/chat/conversations/${encodeURIComponent(conversationId)}/attachment-upload-sessions`,
253
+ body: file
254
+ });
255
+ if (response.status !== 201)
256
+ handleApiError(response, "create_chat_attachment_upload_session");
257
+ return response.body;
258
+ },
259
+ async uploadChatAttachment(session, file) {
260
+ const isSignedUpload = /^https?:/i.test(session.uploadUrl);
261
+ const response = await fetchImpl(resolveUrl(resolveBaseUrl(), session.uploadUrl), {
262
+ method: session.uploadMethod,
263
+ headers: isSignedUpload ? { "content-type": file.type || "application/octet-stream", ...session.uploadHeaders } : await authorizedHeaders(file.type || "application/octet-stream"),
264
+ body: file,
265
+ ...!isSignedUpload ? (0, import_request.buildBrowserCredentialsInit)(includeBrowserCredentials) : {}
266
+ });
267
+ if (!response.ok) {
268
+ const body = await parseResponseBody(response);
269
+ handleApiError({ status: response.status, body, headers: response.headers }, "upload_chat_attachment");
270
+ }
271
+ },
272
+ async completeChatAttachmentUploadSession(sessionId) {
273
+ const response = await request({ method: "POST", path: `/chat/attachment-upload-sessions/${encodeURIComponent(sessionId)}/complete` });
274
+ if (response.status !== 200)
275
+ handleApiError(response, "complete_chat_attachment_upload_session");
276
+ },
277
+ async abortChatAttachmentUploadSession(sessionId) {
278
+ const response = await request({ method: "DELETE", path: `/chat/attachment-upload-sessions/${encodeURIComponent(sessionId)}` });
178
279
  if (response.status !== 204)
179
- handleApiError(response, "revoke_chat_access");
280
+ handleApiError(response, "abort_chat_attachment_upload_session");
281
+ },
282
+ async createCommunityChatChannel(input2) {
283
+ const response = await request({ method: "POST", path: "/admin/chat/community-channels", body: input2 });
284
+ if (response.status !== 201)
285
+ handleApiError(response, "create_community_chat_channel");
286
+ return response.body;
287
+ },
288
+ async updateCommunityChatChannel(conversationId, input2) {
289
+ const response = await request({ method: "PATCH", path: `/admin/chat/community-channels/${encodeURIComponent(conversationId)}`, body: input2 });
290
+ if (response.status !== 200)
291
+ handleApiError(response, "update_community_chat_channel");
292
+ return response.body;
180
293
  },
181
- async setChatVisibility(channel, readPolicy) {
294
+ async fetchAdminDirectConversations(options = {}) {
295
+ const response = await request({ method: "GET", path: `/admin/chat/direct-conversations${listQuery(options)}` });
296
+ if (response.status !== 200)
297
+ handleApiError(response, "fetch_admin_direct_conversations");
298
+ return response.body;
299
+ },
300
+ async fetchAdminDirectConversationMessages(conversationId, options = {}) {
301
+ const response = await request({ method: "GET", path: `/admin/chat/direct-conversations/${encodeURIComponent(conversationId)}/messages${pageQuery(options)}` });
302
+ if (response.status !== 200)
303
+ handleApiError(response, "fetch_admin_direct_conversation_messages");
304
+ return response.body;
305
+ },
306
+ async moderateAdminDirectConversationMessage(conversationId, messageId) {
182
307
  const response = await request({
183
- method: "PATCH",
184
- path: `/admin/chat/channels/${encodeURIComponent(channel)}/read-policy`,
185
- body: { readPolicy }
308
+ method: "DELETE",
309
+ path: `/admin/chat/direct-conversations/${encodeURIComponent(conversationId)}/messages/${encodeURIComponent(messageId)}`
186
310
  });
187
311
  if (response.status !== 200)
188
- handleApiError(response, "set_chat_visibility");
312
+ handleApiError(response, "moderate_admin_direct_conversation_message");
189
313
  return response.body;
190
314
  },
191
315
  async fetchChatAttachment(attachmentId) {
192
- const response = await fetchImpl(resolveUrl(resolveBaseUrl(), `/chat/attachments/${encodeURIComponent(String(attachmentId))}`), {
316
+ const response = await fetchImpl(resolveUrl(resolveBaseUrl(), `/chat/attachments/${encodeURIComponent(attachmentId)}`), {
193
317
  method: "GET",
194
318
  headers: await authorizedHeaders(),
195
319
  ...(0, import_request.buildBrowserCredentialsInit)(includeBrowserCredentials)
@@ -199,8 +323,179 @@ function buildChatApiClientMethods(input) {
199
323
  handleApiError({ status: response.status, body, headers: response.headers }, "fetch_chat_attachment");
200
324
  }
201
325
  return await response.blob();
326
+ },
327
+ async fetchChatChannels() {
328
+ const response = await request({ method: "GET", path: "/chat/channels" });
329
+ if (response.status !== 200)
330
+ handleApiError(response, "fetch_chat_channels");
331
+ return response.body;
332
+ },
333
+ async fetchChatMessages(channel, options = {}) {
334
+ const response = await request({ method: "GET", path: `/chat/channels/${encodeURIComponent(channel)}/messages${pageQuery(options, true)}` });
335
+ if (response.status !== 200)
336
+ handleApiError(response, "fetch_chat_messages");
337
+ return response.body;
338
+ },
339
+ async markChatChannelRead(channel, lastReadMessageId) {
340
+ const response = await request({ method: "PUT", path: `/chat/channels/${encodeURIComponent(channel)}/read`, body: { lastReadMessageId } });
341
+ if (response.status !== 200)
342
+ handleApiError(response, "mark_chat_channel_read");
343
+ return response.body;
344
+ },
345
+ async sendChatMessage(channel, message) {
346
+ const response = await request({ method: "POST", path: `/chat/channels/${encodeURIComponent(channel)}/messages`, body: message });
347
+ if (response.status !== 201)
348
+ handleApiError(response, "send_chat_message");
349
+ return response.body;
350
+ },
351
+ connectChatRealtime(options) {
352
+ let socket = null;
353
+ let reconnectTimer = null;
354
+ let stopped = false;
355
+ let attempt = 0;
356
+ const subscriptions = new Map((options.subscriptions ?? []).map((subscription) => [
357
+ subscription.conversationId,
358
+ subscription.lastEventVersion ?? null
359
+ ]));
360
+ const notifyState = (state) => options.onStateChange?.(state);
361
+ const factory = options.webSocketFactory ?? ((url) => {
362
+ if (typeof globalThis.WebSocket !== "function")
363
+ throw new Error("chat_realtime_websocket_unavailable");
364
+ return new globalThis.WebSocket(url);
365
+ });
366
+ const send = (frame) => {
367
+ if (socket?.readyState === 1)
368
+ socket.send(JSON.stringify(frame));
369
+ };
370
+ const sendSubscriptions = () => {
371
+ for (const [conversationId, lastEventVersion] of subscriptions) {
372
+ send({ type: "subscribe", conversationId, lastEventVersion });
373
+ }
374
+ };
375
+ const scheduleReconnect = () => {
376
+ if (stopped || reconnectTimer)
377
+ return;
378
+ notifyState("reconnecting");
379
+ const exponentialDelay = Math.min(500 * 2 ** Math.min(attempt, 6), 3e4);
380
+ const delay = Math.round(exponentialDelay * (0.8 + Math.random() * 0.4));
381
+ attempt += 1;
382
+ reconnectTimer = setTimeout(() => {
383
+ reconnectTimer = null;
384
+ void connect();
385
+ }, delay);
386
+ };
387
+ const connect = async () => {
388
+ if (stopped)
389
+ return;
390
+ notifyState(attempt === 0 ? "connecting" : "reconnecting");
391
+ let token;
392
+ try {
393
+ token = await resolveToken();
394
+ if (!token)
395
+ throw new Error("chat_realtime_unauthorized");
396
+ socket = factory(chatWebSocketUrl(resolveBaseUrl()));
397
+ } catch (error) {
398
+ const code = error instanceof Error ? error.message : "chat_realtime_connection_failed";
399
+ options.onError?.(code);
400
+ if (code === "chat_realtime_unauthorized") {
401
+ stopped = true;
402
+ notifyState("closed");
403
+ } else
404
+ scheduleReconnect();
405
+ return;
406
+ }
407
+ const activeSocket = socket;
408
+ activeSocket.addEventListener("open", () => {
409
+ if (!stopped && activeSocket === socket)
410
+ activeSocket.send(JSON.stringify({ type: "auth", token }));
411
+ });
412
+ activeSocket.addEventListener("message", (event) => {
413
+ if (stopped || activeSocket !== socket)
414
+ return;
415
+ try {
416
+ const frame = parseRealtimeFrame(event.data);
417
+ if (frame.type === "ready") {
418
+ attempt = 0;
419
+ notifyState("ready");
420
+ sendSubscriptions();
421
+ } else if (frame.type === "subscribed") {
422
+ subscriptions.set(frame.conversationId, frame.eventVersion);
423
+ } else if (frame.type === "conversation.event") {
424
+ try {
425
+ options.onEvent(frame.event);
426
+ subscriptions.set(frame.event.conversationId, frame.event.version);
427
+ } catch {
428
+ options.onError?.("chat_realtime_event_handler_failed");
429
+ activeSocket.close(1011, "chat_realtime_event_handler_failed");
430
+ }
431
+ } else if (frame.type === "inbox.updated") {
432
+ options.onInboxUpdated?.(frame.conversationId);
433
+ } else if (frame.type === "typing.updated") {
434
+ options.onTypingUpdated?.(frame);
435
+ } else if (frame.type === "resync_required") {
436
+ subscriptions.set(frame.conversationId, null);
437
+ options.onResyncRequired?.(frame.conversationId);
438
+ } else {
439
+ options.onError?.(frame.code);
440
+ if (frame.code === "chat_realtime_unauthorized")
441
+ stopped = true;
442
+ }
443
+ } catch (error) {
444
+ options.onError?.(error instanceof Error ? error.message : "chat_realtime_invalid_frame");
445
+ activeSocket.close(1008, "chat_realtime_invalid_frame");
446
+ }
447
+ });
448
+ activeSocket.addEventListener("error", () => {
449
+ if (!stopped && activeSocket === socket)
450
+ options.onError?.("chat_realtime_connection_failed");
451
+ });
452
+ activeSocket.addEventListener("close", (event) => {
453
+ if (activeSocket !== socket)
454
+ return;
455
+ socket = null;
456
+ if (stopped)
457
+ notifyState("closed");
458
+ else if (event.code === 1008) {
459
+ stopped = true;
460
+ options.onError?.(event.reason || "chat_realtime_forbidden");
461
+ notifyState("closed");
462
+ } else
463
+ scheduleReconnect();
464
+ });
465
+ };
466
+ void connect();
467
+ return {
468
+ close() {
469
+ if (stopped)
470
+ return;
471
+ stopped = true;
472
+ if (reconnectTimer)
473
+ clearTimeout(reconnectTimer);
474
+ reconnectTimer = null;
475
+ socket?.close(1e3, "chat_realtime_client_closed");
476
+ socket = null;
477
+ notifyState("closed");
478
+ },
479
+ subscribe(conversationId, lastEventVersion = null) {
480
+ subscriptions.set(conversationId, lastEventVersion);
481
+ send({ type: "subscribe", conversationId, lastEventVersion });
482
+ },
483
+ unsubscribe(conversationId) {
484
+ subscriptions.delete(conversationId);
485
+ send({ type: "unsubscribe", conversationId });
486
+ },
487
+ setTyping(conversationId, active) {
488
+ if (!subscriptions.has(conversationId))
489
+ throw new Error("chat_realtime_not_subscribed");
490
+ send({ type: "typing", conversationId, active });
491
+ },
492
+ getLastEventVersion(conversationId) {
493
+ return subscriptions.get(conversationId) ?? null;
494
+ }
495
+ };
202
496
  }
203
497
  };
498
+ return methods;
204
499
  }
205
500
  // Annotate the CommonJS export names for ESM import in node:
206
501
  0 && (module.exports = {
@@ -3,7 +3,7 @@ export type { ApiClient, ApiClientConfig, ApiRequest, ApiResponse, GameplaySessi
3
3
  import type { FinalizeAppUploadRequest, InitializeAppUploadRequest, RecordAppUploadLaunchCheckRequest, TweakValues } from "@playdrop/types";
4
4
  import type { UploadAppSessionFileOptions } from "./client.js";
5
5
  export type { AdminAgentTaskTranscriptOptions, CreationGamesFetchResult, GetCreationConversationOptions, ListAgentTasksOptions, ListCreatorGamesOptions, UploadWorkerAgentTaskMaterialOptions, } from "./domains/agent-tasks.js";
6
- export type { ChatEditOptions, ChatFileInput, ChatPageOptions, ChatWriteOptions } from "./domains/chat.js";
6
+ export type { ChatEditOptions, ChatFileInput, ChatPageOptions, ChatRealtimeConnection, ChatRealtimeConnectionOptions, ChatRealtimeConnectionState, ChatRealtimeSocket, ChatSystemWriteOptions, ChatWriteOptions, } from "./domains/chat.js";
7
7
  import type { AcquisitionAttributionRequest, AdminCreatorEngagementRulePreviewRequest, AdminGameCollectionCandidatesResponse, AdminGameCollectionResponse, AdminGameCollectionsResponse, AdminMutateAchievementStateRequest, AdminMutateLeaderboardScoreRequest, AgentTaskAttachmentUploadResponse, AgentTaskCreatorFeedbackStatus, AgentTaskKind, AgentTaskNewGameSuggestionsResponse, ApiKeyLoginRequest, AppBrowseRequest, AppListingDraftMediaSlot, AppAuthMode, AppDetailRequest, AppDetailResponse, AppleLinkRequest, AppleNativeChallengeResponse, AppleOAuthHandoffExchangeRequest, AppleOAuthLinkStartRequest, ApplePendingSignupResponse, ApplePushDeviceRequest, AppleSignInRequest, AppLogEntryRequest, AppMoreCollectionKey, AppMoreCollectionPage, AppMoreCollectionRequest, AppMoreCollectionsRequest, AppMoreCollectionsResponse, AppResponse, AppSurface, AppType, AppVersionVisibility, AssetBrowseKind, AssetCategory, AssetListSort, AssetPackListSort, AssetPackResponse, AssetResponse, AssetSpecResponse, AssetSummaryResponse, BoostPurchaseRequest, BoostReportQuery, ClaimFreeCreditRewardResponse, ClearAgentTaskResponse, CliLoginApproveRequest, CliLoginPollRequest, CliWebLaunchStartRequest, CompleteAppleSignupRequest, CompleteGoogleSignupRequest, CompleteXSignupRequest, ContentLicense, CreateAdminGameCollectionRequest, CreateAppAgentTaskRequest, CreateCommentReportRequest, CreateCliApiTokenRequest, CreateContentCommentRequest, CompletePlayTradeSessionRequest, CreatePlayTradeSessionRequest, PlayTradeActivityRequest, CreateContentReportRequest, CreateCreatorReportRequest, CreateNewGameAgentTaskRequest, CreateStaticGameAgentTaskRequest, CreateRemixGameAgentTaskRequest, CreditPackCheckoutSessionRequest, CreatorPlanSessionRequest, CreatorAppsBrowseRequest, CreatorBlockMutationResponse, CreatorEngagementRuleExecutionStatus, CreatorFollowMutationResponse, DeleteMyAccountRequest, DislikedItemMutationResponse, FeedbackCategory, FeedbackClient, FeedbackStatus, FetchHomeOptions, FirebasePushDeviceRequest, FollowingFeedSection, FollowingSort, FreeCreditsResponse, GoogleAppleLinkRequest, GoogleAppleSignInRequest, GoogleOneTapExchangeRequest, GameplaySessionStartRequest, GameplaySessionActivityRequest, IapPurchaseRequest, IapReceiptListParams, InvitePreviewResponse, LibraryCategory, LibrarySort, LikedItemMutationResponse, ModerationReportResponse, NativeOAuthHandoffExchangeRequest, NativeOAuthLinkStartRequest, MyInviteCodeResponse, NotificationStatus, PublicEmailPreferencesUpdateRequest, ResolveRuntimeAssetsRequest, RetryAgentTaskResponse, RegisterAgentBundleRequest, ReviewState, SavedItemKind, SearchRequest, SearchSuggestRequest, SupportedLocale, SubmitCreatorSurveyRequest, SubmitCreatorSurveyResponse, SubmitFeedbackRequest, TrackEngagementEventRequest, TrackEngagementEventResponse, UpdateAdminFeedbackRequest, UpdateAdminGameCollectionGamesRequest, UpdateAdminGameCollectionRequest, UpdateAssetPackRequest, UpdateAssetPackVersionRequest, UpdateAssetRequest, UpdateAssetVersionRequest, UpdateProfileRequest, UpdatePreferredLocaleRequest, UpdateUserCommunicationPreferencesRequest, UpsertWebPushSubscriptionRequest, WorkerAgentTaskAttemptUnavailableRequest, WorkerAcknowledgeAgentTaskClaimRequest, WorkerClaimAgentTaskRequest, WorkerClaimAgentTaskSlugRequest, WorkerCompleteAgentTaskRequest, WorkerCompleteReleaseSmokeRequest, WorkerCreateAgentTaskEventRequest, WorkerCreateAgentTaskTranscriptChunkRequest, WorkerFailAgentTaskRequest, WorkerHeartbeatAgentTaskRequest, WorkerPresenceRequest, WorkerRecordAgentTaskRunTelemetryRequest, WorkerSubmitAgentTaskReviewRequest, WorkerUpdateIntentRequest } from "@playdrop/types";
8
8
  import type { ApiClient } from "./client.js";
9
9
  import type { AdminAgentTaskTranscriptOptions, GetCreationConversationOptions, ListAgentTasksOptions, ListCreatorGamesOptions } from "./domains/agent-tasks.js";