@noverachat/sdk-web 0.0.3 → 0.1.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/dist/index.js CHANGED
@@ -1,3 +1,197 @@
1
+ // src/generated/rest.ts
2
+ function parseAppPolicy(j) {
3
+ return {
4
+ appId: j["app_id"],
5
+ allowMemberBulkDelete: j["allow_member_bulk_delete"],
6
+ readReceiptsEnabled: j["read_receipts_enabled"],
7
+ typingIndicatorsEnabled: j["typing_indicators_enabled"],
8
+ allowMemberDeleteAnyMessage: j["allow_member_delete_any_message"]
9
+ };
10
+ }
11
+ function parseUnreadSummary(j) {
12
+ return {
13
+ total: j["total"],
14
+ rooms: j["rooms"].map((e) => parseUnreadRoomItem(e))
15
+ };
16
+ }
17
+ function parseUnreadRoomItem(j) {
18
+ return {
19
+ roomId: j["room_id"],
20
+ name: j["name"],
21
+ roomType: j["room_type"],
22
+ unreadCount: j["unread_count"],
23
+ lastMessageId: j["last_message_id"],
24
+ lastReadMessageId: j["last_read_message_id"],
25
+ lastMessagePreview: j["last_message_preview"],
26
+ memberCount: j["member_count"],
27
+ membersPreview: j["members_preview"] == null ? void 0 : j["members_preview"].map((e) => parseMemberPreview(e))
28
+ };
29
+ }
30
+ function parseMemberPreview(j) {
31
+ return {
32
+ userId: j["user_id"],
33
+ nickname: j["nickname"],
34
+ profileImageUrl: j["profile_image_url"],
35
+ isOnline: j["is_online"]
36
+ };
37
+ }
38
+ function parseRoomUnread(j) {
39
+ return {
40
+ roomId: j["room_id"],
41
+ unreadCount: j["unread_count"],
42
+ lastReadMessageId: j["last_read_message_id"],
43
+ lastMessageId: j["last_message_id"]
44
+ };
45
+ }
46
+ function parseMessageOut(j) {
47
+ return {
48
+ appId: j["app_id"],
49
+ roomId: j["room_id"],
50
+ messageId: j["message_id"],
51
+ sender: j["sender"] == null ? null : parseSenderMini(j["sender"]),
52
+ senderId: j["sender_id"],
53
+ messageType: j["message_type"],
54
+ customType: j["custom_type"],
55
+ content: j["content"],
56
+ data: j["data"],
57
+ meta: j["meta"],
58
+ fileId: j["file_id"],
59
+ file: j["file"],
60
+ files: j["files"],
61
+ replyToId: j["reply_to_id"],
62
+ reactions: j["reactions"] == null ? void 0 : j["reactions"].map((e) => parseReactionEntry(e)),
63
+ createdAt: j["created_at"],
64
+ updatedAt: j["updated_at"],
65
+ deletedAt: j["deleted_at"],
66
+ deletedByUserId: j["deleted_by_user_id"],
67
+ deleteScope: j["delete_scope"],
68
+ editedCount: j["edited_count"],
69
+ createdAtMs: j["created_at_ms"],
70
+ updatedAtMs: j["updated_at_ms"]
71
+ };
72
+ }
73
+ function parseAnnouncementOut(j) {
74
+ return {
75
+ id: j["id"],
76
+ roomId: j["room_id"],
77
+ content: j["content"],
78
+ createdBy: j["created_by"],
79
+ createdAt: j["created_at"],
80
+ updatedAt: j["updated_at"]
81
+ };
82
+ }
83
+ function parseInviteTokenOut(j) {
84
+ return {
85
+ token: j["token"],
86
+ roomId: j["room_id"],
87
+ createdBy: j["created_by"],
88
+ createdAt: j["created_at"],
89
+ expiresAt: j["expires_at"],
90
+ maxUses: j["max_uses"],
91
+ usedCount: j["used_count"],
92
+ revokedAt: j["revoked_at"]
93
+ };
94
+ }
95
+ function parseInviteAcceptResponse(j) {
96
+ return {
97
+ roomId: j["room_id"],
98
+ roomName: j["room_name"],
99
+ wasAlreadyMember: j["was_already_member"]
100
+ };
101
+ }
102
+ function parseDeviceOut(j) {
103
+ return {
104
+ deviceId: j["device_id"],
105
+ tokenType: j["token_type"],
106
+ tokenPreview: j["token_preview"],
107
+ os: j["os"],
108
+ appVersion: j["app_version"],
109
+ pushEnabled: j["push_enabled"],
110
+ lastActiveAt: j["last_active_at"],
111
+ createdAt: j["created_at"]
112
+ };
113
+ }
114
+ function parseDeviceListResponse(j) {
115
+ return {
116
+ items: j["items"].map((e) => parseDeviceOut(e)),
117
+ total: j["total"]
118
+ };
119
+ }
120
+ function parsePushPreferencesOut(j) {
121
+ return {
122
+ pushEnabled: j["push_enabled"]
123
+ };
124
+ }
125
+ function parseFileOut(j) {
126
+ return {
127
+ id: j["id"],
128
+ kind: j["kind"],
129
+ mime: j["mime"],
130
+ sizeBytes: j["size_bytes"],
131
+ originalName: j["original_name"],
132
+ width: j["width"],
133
+ height: j["height"],
134
+ durationSec: j["duration_sec"],
135
+ thumbnailStatus: j["thumbnail_status"],
136
+ uploadStatus: j["upload_status"],
137
+ createdAt: j["created_at"],
138
+ committedAt: j["committed_at"],
139
+ forwardBlocked: j["forward_blocked"]
140
+ };
141
+ }
142
+ function parseFileCreateResponse(j) {
143
+ return {
144
+ fileId: j["file_id"],
145
+ uploadUrl: j["upload_url"],
146
+ headers: j["headers"],
147
+ expiresAtMs: j["expires_at_ms"]
148
+ };
149
+ }
150
+ function parseBlockOut(j) {
151
+ return {
152
+ blockedUserId: j["blocked_user_id"],
153
+ reason: j["reason"],
154
+ createdAt: j["created_at"]
155
+ };
156
+ }
157
+ function parseRoomMemberOut(j) {
158
+ return {
159
+ userId: j["user_id"],
160
+ nickname: j["nickname"],
161
+ role: j["role"],
162
+ pushTrigger: j["push_trigger"],
163
+ lastReadMessageId: j["last_read_message_id"],
164
+ joinedAt: j["joined_at"],
165
+ isOnline: j["is_online"],
166
+ mutedUntil: j["muted_until"]
167
+ };
168
+ }
169
+ function parseBulkDeleteResult(j) {
170
+ return {
171
+ cleared: j["cleared"],
172
+ upToMessageId: j["up_to_message_id"]
173
+ };
174
+ }
175
+ function parseBulkDeleteBySenderResult(j) {
176
+ return {
177
+ cleared: j["cleared"]
178
+ };
179
+ }
180
+ function parseSenderMini(j) {
181
+ return {
182
+ userId: j["user_id"],
183
+ nickname: j["nickname"],
184
+ profileImageUrl: j["profile_image_url"]
185
+ };
186
+ }
187
+ function parseReactionEntry(j) {
188
+ return {
189
+ key: j["key"],
190
+ userIds: j["user_ids"],
191
+ updatedAt: j["updated_at"]
192
+ };
193
+ }
194
+
1
195
  // src/errors.ts
2
196
  var ErrorCode = {
3
197
  INTERNAL: "NC-GEN-001",
@@ -293,64 +487,31 @@ function tempId() {
293
487
  }
294
488
 
295
489
  // src/features/room.ts
296
- function toMessageOut(r) {
297
- const dataObj = r.data ?? null;
490
+ function toFileInline(f) {
491
+ return {
492
+ id: f.id,
493
+ kind: f.kind,
494
+ mime: f.mime,
495
+ size: f.size,
496
+ name: f.name ?? null,
497
+ width: f.width ?? null,
498
+ height: f.height ?? null,
499
+ durationSec: f.duration_sec ?? null,
500
+ thumbnailStatus: f.thumbnail_status,
501
+ forwardBlocked: Boolean(f.forward_blocked)
502
+ };
503
+ }
504
+ function toMessageOut(j) {
505
+ const base = parseMessageOut(j);
506
+ const raw = j;
507
+ const dataObj = base.data ?? null;
298
508
  const customMeta = dataObj && typeof dataObj === "object" && "_customMeta" in dataObj ? dataObj._customMeta : null;
299
509
  return {
300
- appId: r.app_id,
301
- roomId: r.room_id,
302
- messageId: r.message_id,
303
- sender: r.sender ? {
304
- userId: r.sender.user_id,
305
- nickname: r.sender.nickname ?? null,
306
- profileImageUrl: r.sender.profile_image_url ?? null
307
- } : null,
308
- senderId: r.sender_id ?? null,
309
- messageType: r.message_type,
310
- customType: r.custom_type ?? null,
311
- content: r.content ?? null,
312
- data: dataObj,
313
- meta: r.meta ?? null,
314
- customMeta,
315
- fileId: r.file_id ?? null,
316
- file: r.file ? {
317
- id: r.file.id,
318
- kind: r.file.kind,
319
- mime: r.file.mime,
320
- size: r.file.size,
321
- name: r.file.name ?? null,
322
- width: r.file.width ?? null,
323
- height: r.file.height ?? null,
324
- durationSec: r.file.duration_sec ?? null,
325
- thumbnailStatus: r.file.thumbnail_status,
326
- forwardBlocked: Boolean(r.file.forward_blocked)
327
- } : null,
328
- files: r.files && r.files.length > 0 ? r.files.map((f) => ({
329
- id: f.id,
330
- kind: f.kind,
331
- mime: f.mime,
332
- size: f.size,
333
- name: f.name ?? null,
334
- width: f.width ?? null,
335
- height: f.height ?? null,
336
- durationSec: f.duration_sec ?? null,
337
- thumbnailStatus: f.thumbnail_status,
338
- forwardBlocked: Boolean(f.forward_blocked)
339
- })) : null,
340
- replyToId: r.reply_to_id ?? null,
341
- reactions: (r.reactions ?? []).map((x) => ({
342
- key: x.key,
343
- userIds: x.user_ids,
344
- updatedAt: x.updated_at
345
- })),
346
- createdAt: r.created_at,
347
- createdAtMs: r.created_at_ms,
348
- updatedAt: r.updated_at ?? null,
349
- updatedAtMs: r.updated_at_ms ?? null,
350
- deletedAt: r.deleted_at ?? null,
351
- deletedByUserId: r.deleted_by_user_id ?? null,
352
- deleteScope: r.delete_scope ?? null,
353
- editedCount: r.edited_count ?? 0
510
+ ...base,
511
+ file: raw.file ? toFileInline(raw.file) : null,
512
+ files: raw.files && raw.files.length > 0 ? raw.files.map(toFileInline) : null,
513
+ reactions: base.reactions ?? [],
514
+ customMeta
354
515
  };
355
516
  }
356
517
  var Room = class _Room {
@@ -536,20 +697,24 @@ var Room = class _Room {
536
697
  async sendFile(file, opts = {}) {
537
698
  const kind = opts.kind ?? (file.type.startsWith("image/") ? "image" : file.type.startsWith("video/") ? "video" : "file");
538
699
  const name = opts.name ?? file.name;
539
- const presign = await this.http.request("POST", "/api/v1/files", {
540
- kind,
541
- mime: file.type || "application/octet-stream",
542
- size: file.size,
543
- original_name: name,
544
- // 파일 전달 제한 — uploader-set at presign time. When true,
545
- // other users can't forward this file to a different room.
546
- forward_blocked: Boolean(opts.forwardBlocked)
547
- });
700
+ const created = parseFileCreateResponse(
701
+ await this.http.request("POST", "/api/v1/files", {
702
+ kind,
703
+ mime: file.type || "application/octet-stream",
704
+ size: file.size,
705
+ original_name: name,
706
+ // 파일 전달 제한 uploader-set at presign time. When true,
707
+ // other users can't forward this file to a different room.
708
+ forward_blocked: Boolean(opts.forwardBlocked)
709
+ })
710
+ );
548
711
  const presigned = {
549
- fileId: presign.file_id,
550
- uploadUrl: presign.upload_url,
551
- headers: presign.headers,
552
- expiresAtMs: presign.expires_at_ms
712
+ fileId: created.fileId,
713
+ uploadUrl: created.uploadUrl,
714
+ // Presigned S3 headers are always string-valued; the generated
715
+ // model types the JSON object loosely as Record<string, unknown>.
716
+ headers: created.headers,
717
+ expiresAtMs: created.expiresAtMs
553
718
  };
554
719
  await new Promise((resolve, reject) => {
555
720
  const xhr = new XMLHttpRequest();
@@ -604,17 +769,19 @@ var Room = class _Room {
604
769
  async uploadFileOnly(file, opts = {}) {
605
770
  const kind = opts.kind ?? (file.type.startsWith("image/") ? "image" : file.type.startsWith("video/") ? "video" : "file");
606
771
  const name = opts.name ?? file.name;
607
- const presign = await this.http.request("POST", "/api/v1/files", {
608
- kind,
609
- mime: file.type || "application/octet-stream",
610
- size: file.size,
611
- original_name: name,
612
- forward_blocked: Boolean(opts.forwardBlocked)
613
- });
772
+ const created = parseFileCreateResponse(
773
+ await this.http.request("POST", "/api/v1/files", {
774
+ kind,
775
+ mime: file.type || "application/octet-stream",
776
+ size: file.size,
777
+ original_name: name,
778
+ forward_blocked: Boolean(opts.forwardBlocked)
779
+ })
780
+ );
614
781
  await new Promise((resolve, reject) => {
615
782
  const xhr = new XMLHttpRequest();
616
- xhr.open("PUT", presign.upload_url);
617
- for (const [k, v] of Object.entries(presign.headers)) {
783
+ xhr.open("PUT", created.uploadUrl);
784
+ for (const [k, v] of Object.entries(created.headers)) {
618
785
  xhr.setRequestHeader(k, v);
619
786
  }
620
787
  if (opts.onProgress) {
@@ -641,10 +808,10 @@ var Room = class _Room {
641
808
  });
642
809
  await this.http.request(
643
810
  "POST",
644
- `/api/v1/files/${presign.file_id}/commit`,
811
+ `/api/v1/files/${created.fileId}/commit`,
645
812
  {}
646
813
  );
647
- return presign.file_id;
814
+ return created.fileId;
648
815
  }
649
816
  /**
650
817
  * List files shared in this room — newest-first, paginated.
@@ -669,23 +836,26 @@ var Room = class _Room {
669
836
  }
670
837
  );
671
838
  return {
672
- items: raw.items.map((r) => ({
673
- messageId: r.message_id,
674
- senderId: r.sender_id,
675
- createdAtMs: r.created_at_ms,
676
- file: {
677
- id: r.file.id,
678
- kind: r.file.kind,
679
- mime: r.file.mime,
680
- size: r.file.size_bytes,
681
- name: r.file.original_name ?? null,
682
- width: r.file.width ?? null,
683
- height: r.file.height ?? null,
684
- durationSec: r.file.duration_sec ?? null,
685
- thumbnailStatus: r.file.thumbnail_status,
686
- forwardBlocked: Boolean(r.file.forward_blocked)
687
- }
688
- })),
839
+ items: raw.items.map((r) => {
840
+ const f = parseFileOut(r.file);
841
+ return {
842
+ messageId: r.message_id,
843
+ senderId: r.sender_id,
844
+ createdAtMs: r.created_at_ms,
845
+ file: {
846
+ id: f.id,
847
+ kind: f.kind,
848
+ mime: f.mime,
849
+ size: f.sizeBytes,
850
+ name: f.originalName ?? null,
851
+ width: f.width ?? null,
852
+ height: f.height ?? null,
853
+ durationSec: f.durationSec ?? null,
854
+ thumbnailStatus: f.thumbnailStatus,
855
+ forwardBlocked: Boolean(f.forwardBlocked)
856
+ }
857
+ };
858
+ }),
689
859
  nextCursor: raw.next_cursor,
690
860
  hasMore: raw.has_more
691
861
  };
@@ -723,11 +893,12 @@ var Room = class _Room {
723
893
  this.ws.send({ type: "typing", room_id: this.roomId, is_typing: isTyping });
724
894
  }
725
895
  async edit(messageId, patch) {
726
- return this.http.request(
896
+ const raw = await this.http.request(
727
897
  "PUT",
728
898
  `/api/v1/rooms/${this.roomId}/messages/${messageId}`,
729
899
  patch
730
900
  );
901
+ return toMessageOut(raw);
731
902
  }
732
903
  async delete(messageId, scope = "ALL") {
733
904
  await this.http.request(
@@ -752,12 +923,39 @@ var Room = class _Room {
752
923
  * Other members get a `MEMBER_LEFT` [`roomEvent`](../reference/noverachat.md#events).
753
924
  * Idempotent if already a non-member; throws 409 if the caller was KICKED
754
925
  * (ask an operator to restore first). For GROUP/SUPER_GROUP, re-entry needs
755
- * an operator add; for ONE, re-creating the 1:1 rejoins with history kept. */
756
- async leave() {
926
+ * an operator add; for ONE, re-creating the 1:1 rejoins with history kept.
927
+ *
928
+ * Optional `deleteMessages` — atomically clean up chat history as part
929
+ * of leaving. Default is `"none"` (messages survive so the room's
930
+ * history keeps making sense to remaining members).
931
+ * - `"none"` — leave only.
932
+ * - `"mine"` — soft-delete every message YOU sent, then leave. Other
933
+ * members' messages are untouched. Broadcasts
934
+ * `messages_cleared_by_sender` so connected clients hide
935
+ * them locally.
936
+ * - `"all"` — wipe every message in the room, then leave. Subject to
937
+ * the app's `allow_member_bulk_delete` gate and per-room
938
+ * OPERATOR check — a plain member gets 403 back. */
939
+ async leave(options) {
940
+ const mode = options?.deleteMessages ?? "none";
941
+ const query = mode === "none" ? "" : `?delete_messages=${encodeURIComponent(mode)}`;
757
942
  await this.http.request(
758
943
  "POST",
759
- `/api/v1/rooms/${this.roomId}/leave`
944
+ `/api/v1/rooms/${this.roomId}/leave${query}`
945
+ );
946
+ }
947
+ /** Delete every message YOU sent in this room. Standalone version of
948
+ * `leave({ deleteMessages: "mine" })` — useful when a user wants to
949
+ * scrub their history without actually leaving. Other members'
950
+ * messages are untouched; broadcasts `messages_cleared_by_sender`
951
+ * so connected clients hide the vanished ones locally.
952
+ * No OPERATOR gate — you own your messages. */
953
+ async deleteMyMessages() {
954
+ const raw = await this.http.request(
955
+ "DELETE",
956
+ `/api/v1/rooms/${this.roomId}/messages/mine`
760
957
  );
958
+ return parseBulkDeleteBySenderResult(raw);
761
959
  }
762
960
  /**
763
961
  * Set the caller's per-room push preference — like long-pressing a
@@ -783,11 +981,11 @@ var Room = class _Room {
783
981
  * hide rendered messages up to (and including) that id.
784
982
  */
785
983
  async deleteAllMessages() {
786
- const raw = await this.http.request("DELETE", `/api/v1/rooms/${this.roomId}/messages`);
787
- return {
788
- cleared: raw.cleared,
789
- upToMessageId: raw.up_to_message_id
790
- };
984
+ const raw = await this.http.request(
985
+ "DELETE",
986
+ `/api/v1/rooms/${this.roomId}/messages`
987
+ );
988
+ return parseBulkDeleteResult(raw);
791
989
  }
792
990
  async react(messageId, key) {
793
991
  await this.http.request(
@@ -874,17 +1072,11 @@ var Room = class _Room {
874
1072
  * rooms, so the caller must already be a member to read this.
875
1073
  */
876
1074
  async listMembers() {
877
- const raw = await this.http.request("GET", `/api/v1/rooms/${this.roomId}/members`);
878
- return raw.map((m) => ({
879
- userId: m.user_id,
880
- nickname: m.nickname ?? null,
881
- role: m.role,
882
- pushTrigger: m.push_trigger,
883
- lastReadMessageId: m.last_read_message_id,
884
- joinedAt: m.joined_at,
885
- isOnline: m.is_online ?? null,
886
- mutedUntil: m.muted_until ?? null
887
- }));
1075
+ const raw = await this.http.request(
1076
+ "GET",
1077
+ `/api/v1/rooms/${this.roomId}/members`
1078
+ );
1079
+ return raw.map((m) => parseRoomMemberOut(m));
888
1080
  }
889
1081
  // -------- mute --------
890
1082
  /** OPERATOR-only: mute a member so they can't send messages in this
@@ -912,13 +1104,11 @@ var Room = class _Room {
912
1104
  * newer than the caller's `read_watermark`, plus the room's
913
1105
  * `last_message_id` so the UI can show a "•" dot regardless of count. */
914
1106
  async unread() {
915
- const raw = await this.http.request("GET", `/api/v1/rooms/${this.roomId}/unread`);
916
- return {
917
- roomId: raw.room_id,
918
- unreadCount: raw.unread_count,
919
- lastReadMessageId: raw.last_read_message_id,
920
- lastMessageId: raw.last_message_id
921
- };
1107
+ const raw = await this.http.request(
1108
+ "GET",
1109
+ `/api/v1/rooms/${this.roomId}/unread`
1110
+ );
1111
+ return parseRoomUnread(raw);
922
1112
  }
923
1113
  // -------- freeze --------
924
1114
  /** OPERATOR-only moderation: freeze this room. Non-admin messages are
@@ -953,49 +1143,35 @@ var Room = class _Room {
953
1143
  }
954
1144
  // -------- announcements --------
955
1145
  async listAnnouncements() {
956
- const raw = await this.http.request("GET", `/api/v1/rooms/${this.roomId}/announcements`);
957
- return raw.map((a) => ({
958
- id: a.id,
959
- roomId: a.room_id,
960
- content: a.content,
961
- createdBy: a.created_by,
962
- createdAt: a.created_at,
963
- updatedAt: a.updated_at
964
- }));
1146
+ const raw = await this.http.request(
1147
+ "GET",
1148
+ `/api/v1/rooms/${this.roomId}/announcements`
1149
+ );
1150
+ return raw.map((a) => parseAnnouncementOut(a));
965
1151
  }
966
1152
  async getCurrentAnnouncement() {
967
- const raw = await this.http.request("GET", `/api/v1/rooms/${this.roomId}/announcements/current`);
1153
+ const raw = await this.http.request(
1154
+ "GET",
1155
+ `/api/v1/rooms/${this.roomId}/announcements/current`
1156
+ );
968
1157
  if (!raw) return null;
969
- return {
970
- id: raw.id,
971
- roomId: raw.room_id,
972
- content: raw.content,
973
- createdBy: raw.created_by,
974
- createdAt: raw.created_at,
975
- updatedAt: raw.updated_at
976
- };
1158
+ return parseAnnouncementOut(raw);
977
1159
  }
978
1160
  async postAnnouncement(content) {
979
- const raw = await this.http.request("POST", `/api/v1/rooms/${this.roomId}/announcements`, { content });
980
- return {
981
- id: raw.id,
982
- roomId: raw.room_id,
983
- content: raw.content,
984
- createdBy: raw.created_by,
985
- createdAt: raw.created_at,
986
- updatedAt: raw.updated_at
987
- };
1161
+ const raw = await this.http.request(
1162
+ "POST",
1163
+ `/api/v1/rooms/${this.roomId}/announcements`,
1164
+ { content }
1165
+ );
1166
+ return parseAnnouncementOut(raw);
988
1167
  }
989
1168
  async updateAnnouncement(id, content) {
990
- const raw = await this.http.request("PATCH", `/api/v1/rooms/${this.roomId}/announcements/${id}`, { content });
991
- return {
992
- id: raw.id,
993
- roomId: raw.room_id,
994
- content: raw.content,
995
- createdBy: raw.created_by,
996
- createdAt: raw.created_at,
997
- updatedAt: raw.updated_at
998
- };
1169
+ const raw = await this.http.request(
1170
+ "PATCH",
1171
+ `/api/v1/rooms/${this.roomId}/announcements/${id}`,
1172
+ { content }
1173
+ );
1174
+ return parseAnnouncementOut(raw);
999
1175
  }
1000
1176
  async deleteAnnouncement(id) {
1001
1177
  await this.http.request(
@@ -1017,32 +1193,21 @@ var Room = class _Room {
1017
1193
  const body = {};
1018
1194
  if (opts && "expiresInHours" in opts) body.expires_in_hours = opts.expiresInHours;
1019
1195
  if (opts && "maxUses" in opts) body.max_uses = opts.maxUses;
1020
- const raw = await this.http.request("POST", `/api/v1/rooms/${this.roomId}/invites`, body);
1021
- return {
1022
- token: raw.token,
1023
- roomId: raw.room_id,
1024
- createdBy: raw.created_by,
1025
- createdAt: raw.created_at,
1026
- expiresAt: raw.expires_at,
1027
- maxUses: raw.max_uses,
1028
- usedCount: raw.used_count,
1029
- revokedAt: raw.revoked_at
1030
- };
1196
+ const raw = await this.http.request(
1197
+ "POST",
1198
+ `/api/v1/rooms/${this.roomId}/invites`,
1199
+ body
1200
+ );
1201
+ return parseInviteTokenOut(raw);
1031
1202
  }
1032
1203
  /** OPERATOR-only: list all invite tokens for this room (including
1033
1204
  * revoked / expired / exhausted), newest first. */
1034
1205
  async listInvites() {
1035
- const raw = await this.http.request("GET", `/api/v1/rooms/${this.roomId}/invites`);
1036
- return raw.map((r) => ({
1037
- token: r.token,
1038
- roomId: r.room_id,
1039
- createdBy: r.created_by,
1040
- createdAt: r.created_at,
1041
- expiresAt: r.expires_at,
1042
- maxUses: r.max_uses,
1043
- usedCount: r.used_count,
1044
- revokedAt: r.revoked_at
1045
- }));
1206
+ const raw = await this.http.request(
1207
+ "GET",
1208
+ `/api/v1/rooms/${this.roomId}/invites`
1209
+ );
1210
+ return raw.map((r) => parseInviteTokenOut(r));
1046
1211
  }
1047
1212
  /** OPERATOR-only: revoke a token immediately. Idempotent — revoking
1048
1213
  * an already-revoked token returns 204 as well. */
@@ -1300,13 +1465,7 @@ var NoveraChat = class {
1300
1465
  * destructive buttons to expose. */
1301
1466
  async appPolicy() {
1302
1467
  const raw = await this.http.request("GET", "/api/v1/app/policy");
1303
- return {
1304
- appId: raw.app_id,
1305
- allowMemberBulkDelete: raw.allow_member_bulk_delete,
1306
- readReceiptsEnabled: raw.read_receipts_enabled ?? true,
1307
- typingIndicatorsEnabled: raw.typing_indicators_enabled ?? true,
1308
- deleteAnyMessageEnabled: raw.allow_member_delete_any_message ?? false
1309
- };
1468
+ return parseAppPolicy(raw);
1310
1469
  }
1311
1470
  // ---- device tokens (push) ----
1312
1471
  /**
@@ -1316,19 +1475,23 @@ var NoveraChat = class {
1316
1475
  * safe. Returns the stored device record (token shown only as a prefix).
1317
1476
  */
1318
1477
  async registerDevice(params) {
1319
- const raw = await this.http.request("POST", "/api/v1/users/me/devices", {
1320
- device_id: params.deviceId,
1321
- token: params.token,
1322
- token_type: params.tokenType,
1323
- os: params.os ?? null,
1324
- app_version: params.appVersion ?? null
1325
- });
1326
- return this._toDeviceInfo(raw);
1478
+ const raw = await this.http.request(
1479
+ "POST",
1480
+ "/api/v1/users/me/devices",
1481
+ {
1482
+ device_id: params.deviceId,
1483
+ token: params.token,
1484
+ token_type: params.tokenType,
1485
+ os: params.os ?? null,
1486
+ app_version: params.appVersion ?? null
1487
+ }
1488
+ );
1489
+ return parseDeviceOut(raw);
1327
1490
  }
1328
1491
  /** List the devices registered for the authenticated user. */
1329
1492
  async listDevices() {
1330
1493
  const raw = await this.http.request("GET", "/api/v1/users/me/devices");
1331
- return raw.items.map((d) => this._toDeviceInfo(d));
1494
+ return parseDeviceListResponse(raw).items;
1332
1495
  }
1333
1496
  /**
1334
1497
  * Turn push delivery on/off for one device without unregistering it —
@@ -1366,7 +1529,7 @@ var NoveraChat = class {
1366
1529
  "GET",
1367
1530
  "/api/v1/users/me/push-preferences"
1368
1531
  );
1369
- return { pushEnabled: Boolean(raw.push_enabled) };
1532
+ return parsePushPreferencesOut(raw);
1370
1533
  }
1371
1534
  /**
1372
1535
  * Flip the caller's global push kill switch. Pass `false` to mute
@@ -1379,7 +1542,7 @@ var NoveraChat = class {
1379
1542
  "/api/v1/users/me/push-preferences",
1380
1543
  { push_enabled: enabled }
1381
1544
  );
1382
- return { pushEnabled: Boolean(raw.push_enabled) };
1545
+ return parsePushPreferencesOut(raw);
1383
1546
  }
1384
1547
  // -------------------------------------------------------------------------
1385
1548
  // User blocks — KakaoTalk-style one-way block.
@@ -1403,11 +1566,7 @@ var NoveraChat = class {
1403
1566
  `/api/v1/users/me/blocks/${encodeURIComponent(userId)}`,
1404
1567
  { reason: reason ?? null }
1405
1568
  );
1406
- return {
1407
- blockedUserId: raw.blocked_user_id,
1408
- reason: raw.reason,
1409
- createdAt: raw.created_at
1410
- };
1569
+ return parseBlockOut(raw);
1411
1570
  }
1412
1571
  /** Unblock a user. Idempotent — resolves either way. */
1413
1572
  async unblockUser(userId) {
@@ -1419,23 +1578,7 @@ var NoveraChat = class {
1419
1578
  /** List every user the caller has blocked, newest first. */
1420
1579
  async listBlocks() {
1421
1580
  const raw = await this.http.request("GET", "/api/v1/users/me/blocks");
1422
- return raw.map((r) => ({
1423
- blockedUserId: r.blocked_user_id,
1424
- reason: r.reason,
1425
- createdAt: r.created_at
1426
- }));
1427
- }
1428
- _toDeviceInfo(raw) {
1429
- return {
1430
- deviceId: raw.device_id,
1431
- tokenType: raw.token_type,
1432
- tokenPreview: raw.token_preview,
1433
- os: raw.os,
1434
- appVersion: raw.app_version,
1435
- pushEnabled: raw.push_enabled,
1436
- lastActiveAt: raw.last_active_at,
1437
- createdAt: raw.created_at
1438
- };
1581
+ return raw.map((r) => parseBlockOut(r));
1439
1582
  }
1440
1583
  /**
1441
1584
  * Authorization-gated URL for downloading a file attachment.
@@ -1493,41 +1636,27 @@ var NoveraChat = class {
1493
1636
  * shipping date.
1494
1637
  */
1495
1638
  async getFileMeta(fileId) {
1496
- const raw = await this.http.request("GET", `/api/v1/files/${encodeURIComponent(fileId)}/meta`);
1639
+ const raw = await this.http.request(
1640
+ "GET",
1641
+ `/api/v1/files/${encodeURIComponent(fileId)}/meta`
1642
+ );
1643
+ const f = parseFileOut(raw);
1497
1644
  return {
1498
- id: raw.id,
1499
- kind: raw.kind,
1500
- mime: raw.mime,
1501
- size: raw.size_bytes,
1502
- name: raw.original_name ?? null,
1503
- width: raw.width ?? null,
1504
- height: raw.height ?? null,
1505
- durationSec: raw.duration_sec ?? null,
1506
- thumbnailStatus: raw.thumbnail_status,
1507
- forwardBlocked: Boolean(raw.forward_blocked)
1645
+ id: f.id,
1646
+ kind: f.kind,
1647
+ mime: f.mime,
1648
+ size: f.sizeBytes,
1649
+ name: f.originalName ?? null,
1650
+ width: f.width ?? null,
1651
+ height: f.height ?? null,
1652
+ durationSec: f.durationSec ?? null,
1653
+ thumbnailStatus: f.thumbnailStatus,
1654
+ forwardBlocked: Boolean(f.forwardBlocked)
1508
1655
  };
1509
1656
  }
1510
1657
  async unreadSummary() {
1511
1658
  const raw = await this.http.request("GET", "/api/v1/users/me/unread");
1512
- return {
1513
- total: raw.total,
1514
- rooms: raw.rooms.map((r) => ({
1515
- roomId: r.room_id,
1516
- name: r.name,
1517
- roomType: r.room_type ?? null,
1518
- unreadCount: r.unread_count,
1519
- lastMessageId: r.last_message_id,
1520
- lastMessagePreview: r.last_message_preview ?? null,
1521
- lastReadMessageId: r.last_read_message_id,
1522
- memberCount: r.member_count ?? 0,
1523
- members: (r.members_preview ?? []).map((m) => ({
1524
- userId: m.user_id,
1525
- nickname: m.nickname ?? null,
1526
- profileImageUrl: m.profile_image_url ?? null,
1527
- isOnline: m.is_online ?? null
1528
- }))
1529
- }))
1530
- };
1659
+ return parseUnreadSummary(raw);
1531
1660
  }
1532
1661
  /**
1533
1662
  * Accept an invite link — call this in flows where you receive a
@@ -1545,12 +1674,11 @@ var NoveraChat = class {
1545
1674
  * kicked from the target room.
1546
1675
  */
1547
1676
  async acceptInvite(token) {
1548
- const raw = await this.http.request("POST", `/api/v1/invites/${encodeURIComponent(token)}/accept`);
1549
- return {
1550
- roomId: raw.room_id,
1551
- roomName: raw.room_name,
1552
- wasAlreadyMember: raw.was_already_member
1553
- };
1677
+ const raw = await this.http.request(
1678
+ "POST",
1679
+ `/api/v1/invites/${encodeURIComponent(token)}/accept`
1680
+ );
1681
+ return parseInviteAcceptResponse(raw);
1554
1682
  }
1555
1683
  // ---- internal ----
1556
1684
  dispatch(msg) {