@noverachat/sdk-web 0.5.2 → 0.6.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.cjs CHANGED
@@ -620,6 +620,7 @@ function chatMessageFromHistory(m) {
620
620
  messageType: m.messageType,
621
621
  sender: m.sender ?? null,
622
622
  customType: m.customType ?? null,
623
+ data: m.data ?? null,
623
624
  fileId: m.fileId ?? null,
624
625
  file: m.file ?? null,
625
626
  files: m.files ?? null,
@@ -644,6 +645,7 @@ function chatMessageFromLive(f) {
644
645
  messageType: f.message_type,
645
646
  sender: null,
646
647
  customType: f.custom_type ?? null,
648
+ data,
647
649
  fileId: f.file_id ?? null,
648
650
  file: f.file ? fileInlineFromWire(f.file) : null,
649
651
  files: f.files ? f.files.map(fileInlineFromWire) : null,
@@ -666,6 +668,7 @@ function pendingChatMessage(p) {
666
668
  messageType: "TEXT",
667
669
  sender: null,
668
670
  customType: p.customType ?? null,
671
+ data: p.data ?? null,
669
672
  fileId: null,
670
673
  file: null,
671
674
  files: null,
@@ -689,6 +692,7 @@ function pendingFileChatMessage(p) {
689
692
  messageType: "FILE",
690
693
  sender: null,
691
694
  customType: null,
695
+ data: null,
692
696
  fileId: null,
693
697
  file: {
694
698
  id: "",
@@ -912,6 +916,7 @@ var MessageCollection = class {
912
916
  content: text,
913
917
  ...opts?.replyToId ? { replyToId: opts.replyToId } : {},
914
918
  ...opts?.customType ? { customType: opts.customType } : {},
919
+ ...opts?.data ? { data: opts.data } : {},
915
920
  ...opts?.customMeta ? { customMeta: opts.customMeta } : {}
916
921
  });
917
922
  tempId2 = sent.tempId;
@@ -2066,6 +2071,16 @@ var Room = class _Room {
2066
2071
  this.pendingAcks.delete(tempId2);
2067
2072
  this.mySentTempIds.delete(tempId2);
2068
2073
  }
2074
+ /**
2075
+ * ack 를 기다리던 전송을 전부 실패 처리한다 — `chat.disconnect()` 처럼
2076
+ * 응답이 올 길이 사라졌을 때. 메시지 컬렉션은 건드리지 않는다(낙관적
2077
+ * 버블은 `failed` 로 바뀌어 재전송 UI 를 띄울 수 있다).
2078
+ */
2079
+ _failPendingSends(reason) {
2080
+ for (const tempId2 of [...this.pendingAcks.keys()]) {
2081
+ this._rejectPending(tempId2, new Error(reason));
2082
+ }
2083
+ }
2069
2084
  /**
2070
2085
  * Try to re-send a pending frame after a transient server reject
2071
2086
  * (currently only NC-RATE-001 — the tenant's per-second token bucket
@@ -2226,9 +2241,22 @@ var NoveraChat = class {
2226
2241
  async connect() {
2227
2242
  await this.ws.connect();
2228
2243
  }
2244
+ /**
2245
+ * 소켓을 닫는다. **Room 파사드와 그 메시지 상태는 유지한다** — `connect()`
2246
+ * 로 다시 붙으면 같은 파사드가 그대로 이어서 프레임을 받는다.
2247
+ *
2248
+ * 예전엔 여기서 `rooms.clear()` 를 했는데, 그러면 UI 가 들고 있던 컬렉션이
2249
+ * 다음 접근 때 빈 컬렉션으로 교체돼 이미 로드한 히스토리가 사라졌다
2250
+ * (React StrictMode 의 connect → disconnect → connect 에서 채팅방이 빈
2251
+ * 화면으로 뜨는 증상). 소켓은 재사용되는 `ReconnectingWs` 인스턴스라
2252
+ * 파사드가 참조를 유지해도 문제가 없다.
2253
+ *
2254
+ * 다만 ack 를 기다리던 전송은 응답 받을 길이 없으므로 여기서 실패로
2255
+ * 정리한다(낙관적 버블이 영원히 `sending` 으로 남지 않도록).
2256
+ */
2229
2257
  disconnect() {
2230
2258
  this.ws.close();
2231
- this.rooms.clear();
2259
+ for (const room of this.rooms.values()) room._failPendingSends("disconnected");
2232
2260
  }
2233
2261
  room(roomId) {
2234
2262
  let r = this.rooms.get(roomId);
package/dist/index.d.cts CHANGED
@@ -723,6 +723,15 @@ interface ChatMessage {
723
723
  sender: SenderMini | null;
724
724
  /** App-defined subtype (e.g. a message-priority convention). */
725
725
  customType: string | null;
726
+ /**
727
+ * 서버가 그대로 통과시키는 앱 정의 페이로드 원문.
728
+ *
729
+ * 테넌트가 `customType` 과 짝지어 쓰는 규약이 여기 담긴다 — 예: 이미지
730
+ * 메시지를 `customType: "IMAGE"` + `data: { kind, fileId, url, thumbUrl }`
731
+ * 로 표현하는 식. SDK 는 내용을 해석하지 않고 원문을 넘긴다.
732
+ * ([customMeta] 는 이 안의 예약 키 `_customMeta` 를 꺼낸 편의 필드다.)
733
+ */
734
+ data: Record<string, unknown> | null;
726
735
  /** Snowflake id (string) of the attached file, when this is a file message. */
727
736
  fileId: string | null;
728
737
  /** Inline metadata of the attached file — enough to render a placeholder
@@ -758,6 +767,7 @@ declare function pendingChatMessage(p: {
758
767
  content: string;
759
768
  replyToId?: string;
760
769
  customType?: string;
770
+ data?: Record<string, unknown>;
761
771
  customMeta?: Record<string, unknown>;
762
772
  }): ChatMessage;
763
773
  /** An optimistic bubble for a FILE message whose bytes are still uploading.
@@ -898,6 +908,10 @@ declare class MessageCollection {
898
908
  send(text: string, opts?: {
899
909
  replyToId?: string;
900
910
  customType?: string;
911
+ /** 앱 정의 페이로드 원문 — `customType` 과 짝지어 쓰는 테넌트 규약용
912
+ * (예: `customType:"IMAGE"` + `data:{kind,fileId,url,thumbUrl}`).
913
+ * 낙관적 버블에도 그대로 실려 전송 즉시 렌더할 수 있다. */
914
+ data?: Record<string, unknown>;
901
915
  customMeta?: Record<string, unknown>;
902
916
  }): void;
903
917
  /** Send a file optimistically. A FILE bubble with `uploadProgress`
@@ -1348,6 +1362,12 @@ declare class Room {
1348
1362
  * fail the matching outbound promise immediately instead of waiting
1349
1363
  * for the 15s ack timeout. */
1350
1364
  _rejectPending(tempId: string, err: Error): void;
1365
+ /**
1366
+ * ack 를 기다리던 전송을 전부 실패 처리한다 — `chat.disconnect()` 처럼
1367
+ * 응답이 올 길이 사라졌을 때. 메시지 컬렉션은 건드리지 않는다(낙관적
1368
+ * 버블은 `failed` 로 바뀌어 재전송 UI 를 띄울 수 있다).
1369
+ */
1370
+ _failPendingSends(reason: string): void;
1351
1371
  /**
1352
1372
  * Try to re-send a pending frame after a transient server reject
1353
1373
  * (currently only NC-RATE-001 — the tenant's per-second token bucket
@@ -1426,6 +1446,19 @@ declare class NoveraChat {
1426
1446
  private lastPresenceKey;
1427
1447
  constructor(opts: ClientOptions);
1428
1448
  connect(): Promise<void>;
1449
+ /**
1450
+ * 소켓을 닫는다. **Room 파사드와 그 메시지 상태는 유지한다** — `connect()`
1451
+ * 로 다시 붙으면 같은 파사드가 그대로 이어서 프레임을 받는다.
1452
+ *
1453
+ * 예전엔 여기서 `rooms.clear()` 를 했는데, 그러면 UI 가 들고 있던 컬렉션이
1454
+ * 다음 접근 때 빈 컬렉션으로 교체돼 이미 로드한 히스토리가 사라졌다
1455
+ * (React StrictMode 의 connect → disconnect → connect 에서 채팅방이 빈
1456
+ * 화면으로 뜨는 증상). 소켓은 재사용되는 `ReconnectingWs` 인스턴스라
1457
+ * 파사드가 참조를 유지해도 문제가 없다.
1458
+ *
1459
+ * 다만 ack 를 기다리던 전송은 응답 받을 길이 없으므로 여기서 실패로
1460
+ * 정리한다(낙관적 버블이 영원히 `sending` 으로 남지 않도록).
1461
+ */
1429
1462
  disconnect(): void;
1430
1463
  room(roomId: string): Room;
1431
1464
  /** Subscribe to client-level events. Currently `roomEvent` — membership /
package/dist/index.d.ts CHANGED
@@ -723,6 +723,15 @@ interface ChatMessage {
723
723
  sender: SenderMini | null;
724
724
  /** App-defined subtype (e.g. a message-priority convention). */
725
725
  customType: string | null;
726
+ /**
727
+ * 서버가 그대로 통과시키는 앱 정의 페이로드 원문.
728
+ *
729
+ * 테넌트가 `customType` 과 짝지어 쓰는 규약이 여기 담긴다 — 예: 이미지
730
+ * 메시지를 `customType: "IMAGE"` + `data: { kind, fileId, url, thumbUrl }`
731
+ * 로 표현하는 식. SDK 는 내용을 해석하지 않고 원문을 넘긴다.
732
+ * ([customMeta] 는 이 안의 예약 키 `_customMeta` 를 꺼낸 편의 필드다.)
733
+ */
734
+ data: Record<string, unknown> | null;
726
735
  /** Snowflake id (string) of the attached file, when this is a file message. */
727
736
  fileId: string | null;
728
737
  /** Inline metadata of the attached file — enough to render a placeholder
@@ -758,6 +767,7 @@ declare function pendingChatMessage(p: {
758
767
  content: string;
759
768
  replyToId?: string;
760
769
  customType?: string;
770
+ data?: Record<string, unknown>;
761
771
  customMeta?: Record<string, unknown>;
762
772
  }): ChatMessage;
763
773
  /** An optimistic bubble for a FILE message whose bytes are still uploading.
@@ -898,6 +908,10 @@ declare class MessageCollection {
898
908
  send(text: string, opts?: {
899
909
  replyToId?: string;
900
910
  customType?: string;
911
+ /** 앱 정의 페이로드 원문 — `customType` 과 짝지어 쓰는 테넌트 규약용
912
+ * (예: `customType:"IMAGE"` + `data:{kind,fileId,url,thumbUrl}`).
913
+ * 낙관적 버블에도 그대로 실려 전송 즉시 렌더할 수 있다. */
914
+ data?: Record<string, unknown>;
901
915
  customMeta?: Record<string, unknown>;
902
916
  }): void;
903
917
  /** Send a file optimistically. A FILE bubble with `uploadProgress`
@@ -1348,6 +1362,12 @@ declare class Room {
1348
1362
  * fail the matching outbound promise immediately instead of waiting
1349
1363
  * for the 15s ack timeout. */
1350
1364
  _rejectPending(tempId: string, err: Error): void;
1365
+ /**
1366
+ * ack 를 기다리던 전송을 전부 실패 처리한다 — `chat.disconnect()` 처럼
1367
+ * 응답이 올 길이 사라졌을 때. 메시지 컬렉션은 건드리지 않는다(낙관적
1368
+ * 버블은 `failed` 로 바뀌어 재전송 UI 를 띄울 수 있다).
1369
+ */
1370
+ _failPendingSends(reason: string): void;
1351
1371
  /**
1352
1372
  * Try to re-send a pending frame after a transient server reject
1353
1373
  * (currently only NC-RATE-001 — the tenant's per-second token bucket
@@ -1426,6 +1446,19 @@ declare class NoveraChat {
1426
1446
  private lastPresenceKey;
1427
1447
  constructor(opts: ClientOptions);
1428
1448
  connect(): Promise<void>;
1449
+ /**
1450
+ * 소켓을 닫는다. **Room 파사드와 그 메시지 상태는 유지한다** — `connect()`
1451
+ * 로 다시 붙으면 같은 파사드가 그대로 이어서 프레임을 받는다.
1452
+ *
1453
+ * 예전엔 여기서 `rooms.clear()` 를 했는데, 그러면 UI 가 들고 있던 컬렉션이
1454
+ * 다음 접근 때 빈 컬렉션으로 교체돼 이미 로드한 히스토리가 사라졌다
1455
+ * (React StrictMode 의 connect → disconnect → connect 에서 채팅방이 빈
1456
+ * 화면으로 뜨는 증상). 소켓은 재사용되는 `ReconnectingWs` 인스턴스라
1457
+ * 파사드가 참조를 유지해도 문제가 없다.
1458
+ *
1459
+ * 다만 ack 를 기다리던 전송은 응답 받을 길이 없으므로 여기서 실패로
1460
+ * 정리한다(낙관적 버블이 영원히 `sending` 으로 남지 않도록).
1461
+ */
1429
1462
  disconnect(): void;
1430
1463
  room(roomId: string): Room;
1431
1464
  /** Subscribe to client-level events. Currently `roomEvent` — membership /
package/dist/index.js CHANGED
@@ -585,6 +585,7 @@ function chatMessageFromHistory(m) {
585
585
  messageType: m.messageType,
586
586
  sender: m.sender ?? null,
587
587
  customType: m.customType ?? null,
588
+ data: m.data ?? null,
588
589
  fileId: m.fileId ?? null,
589
590
  file: m.file ?? null,
590
591
  files: m.files ?? null,
@@ -609,6 +610,7 @@ function chatMessageFromLive(f) {
609
610
  messageType: f.message_type,
610
611
  sender: null,
611
612
  customType: f.custom_type ?? null,
613
+ data,
612
614
  fileId: f.file_id ?? null,
613
615
  file: f.file ? fileInlineFromWire(f.file) : null,
614
616
  files: f.files ? f.files.map(fileInlineFromWire) : null,
@@ -631,6 +633,7 @@ function pendingChatMessage(p) {
631
633
  messageType: "TEXT",
632
634
  sender: null,
633
635
  customType: p.customType ?? null,
636
+ data: p.data ?? null,
634
637
  fileId: null,
635
638
  file: null,
636
639
  files: null,
@@ -654,6 +657,7 @@ function pendingFileChatMessage(p) {
654
657
  messageType: "FILE",
655
658
  sender: null,
656
659
  customType: null,
660
+ data: null,
657
661
  fileId: null,
658
662
  file: {
659
663
  id: "",
@@ -877,6 +881,7 @@ var MessageCollection = class {
877
881
  content: text,
878
882
  ...opts?.replyToId ? { replyToId: opts.replyToId } : {},
879
883
  ...opts?.customType ? { customType: opts.customType } : {},
884
+ ...opts?.data ? { data: opts.data } : {},
880
885
  ...opts?.customMeta ? { customMeta: opts.customMeta } : {}
881
886
  });
882
887
  tempId2 = sent.tempId;
@@ -2031,6 +2036,16 @@ var Room = class _Room {
2031
2036
  this.pendingAcks.delete(tempId2);
2032
2037
  this.mySentTempIds.delete(tempId2);
2033
2038
  }
2039
+ /**
2040
+ * ack 를 기다리던 전송을 전부 실패 처리한다 — `chat.disconnect()` 처럼
2041
+ * 응답이 올 길이 사라졌을 때. 메시지 컬렉션은 건드리지 않는다(낙관적
2042
+ * 버블은 `failed` 로 바뀌어 재전송 UI 를 띄울 수 있다).
2043
+ */
2044
+ _failPendingSends(reason) {
2045
+ for (const tempId2 of [...this.pendingAcks.keys()]) {
2046
+ this._rejectPending(tempId2, new Error(reason));
2047
+ }
2048
+ }
2034
2049
  /**
2035
2050
  * Try to re-send a pending frame after a transient server reject
2036
2051
  * (currently only NC-RATE-001 — the tenant's per-second token bucket
@@ -2191,9 +2206,22 @@ var NoveraChat = class {
2191
2206
  async connect() {
2192
2207
  await this.ws.connect();
2193
2208
  }
2209
+ /**
2210
+ * 소켓을 닫는다. **Room 파사드와 그 메시지 상태는 유지한다** — `connect()`
2211
+ * 로 다시 붙으면 같은 파사드가 그대로 이어서 프레임을 받는다.
2212
+ *
2213
+ * 예전엔 여기서 `rooms.clear()` 를 했는데, 그러면 UI 가 들고 있던 컬렉션이
2214
+ * 다음 접근 때 빈 컬렉션으로 교체돼 이미 로드한 히스토리가 사라졌다
2215
+ * (React StrictMode 의 connect → disconnect → connect 에서 채팅방이 빈
2216
+ * 화면으로 뜨는 증상). 소켓은 재사용되는 `ReconnectingWs` 인스턴스라
2217
+ * 파사드가 참조를 유지해도 문제가 없다.
2218
+ *
2219
+ * 다만 ack 를 기다리던 전송은 응답 받을 길이 없으므로 여기서 실패로
2220
+ * 정리한다(낙관적 버블이 영원히 `sending` 으로 남지 않도록).
2221
+ */
2194
2222
  disconnect() {
2195
2223
  this.ws.close();
2196
- this.rooms.clear();
2224
+ for (const room of this.rooms.values()) room._failPendingSends("disconnected");
2197
2225
  }
2198
2226
  room(roomId) {
2199
2227
  let r = this.rooms.get(roomId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noverachat/sdk-web",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "NoveraChat browser/Node SDK — WebSocket + REST client with optimistic UI, reconnection and gap-fill",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,6 +44,15 @@ export interface ChatMessage {
44
44
  sender: SenderMini | null;
45
45
  /** App-defined subtype (e.g. a message-priority convention). */
46
46
  customType: string | null;
47
+ /**
48
+ * 서버가 그대로 통과시키는 앱 정의 페이로드 원문.
49
+ *
50
+ * 테넌트가 `customType` 과 짝지어 쓰는 규약이 여기 담긴다 — 예: 이미지
51
+ * 메시지를 `customType: "IMAGE"` + `data: { kind, fileId, url, thumbUrl }`
52
+ * 로 표현하는 식. SDK 는 내용을 해석하지 않고 원문을 넘긴다.
53
+ * ([customMeta] 는 이 안의 예약 키 `_customMeta` 를 꺼낸 편의 필드다.)
54
+ */
55
+ data: Record<string, unknown> | null;
47
56
  /** Snowflake id (string) of the attached file, when this is a file message. */
48
57
  fileId: string | null;
49
58
  /** Inline metadata of the attached file — enough to render a placeholder
@@ -96,6 +105,7 @@ export function chatMessageFromHistory(m: MessageOut): ChatMessage {
96
105
  messageType: m.messageType,
97
106
  sender: m.sender ?? null,
98
107
  customType: m.customType ?? null,
108
+ data: m.data ?? null,
99
109
  fileId: m.fileId ?? null,
100
110
  file: m.file ?? null,
101
111
  files: m.files ?? null,
@@ -127,6 +137,7 @@ export function chatMessageFromLive(f: WsChatReceive): ChatMessage {
127
137
  messageType: f.message_type,
128
138
  sender: null,
129
139
  customType: f.custom_type ?? null,
140
+ data,
130
141
  fileId: f.file_id ?? null,
131
142
  file: f.file ? fileInlineFromWire(f.file) : null,
132
143
  files: f.files ? f.files.map(fileInlineFromWire) : null,
@@ -146,6 +157,7 @@ export function pendingChatMessage(p: {
146
157
  content: string;
147
158
  replyToId?: string;
148
159
  customType?: string;
160
+ data?: Record<string, unknown>;
149
161
  customMeta?: Record<string, unknown>;
150
162
  }): ChatMessage {
151
163
  return {
@@ -157,6 +169,7 @@ export function pendingChatMessage(p: {
157
169
  messageType: "TEXT",
158
170
  sender: null,
159
171
  customType: p.customType ?? null,
172
+ data: p.data ?? null,
160
173
  fileId: null,
161
174
  file: null,
162
175
  files: null,
@@ -188,6 +201,7 @@ export function pendingFileChatMessage(p: {
188
201
  messageType: "FILE",
189
202
  sender: null,
190
203
  customType: null,
204
+ data: null,
191
205
  fileId: null,
192
206
  file: {
193
207
  id: "",
package/src/client.ts CHANGED
@@ -149,9 +149,22 @@ export class NoveraChat {
149
149
  await this.ws.connect();
150
150
  }
151
151
 
152
+ /**
153
+ * 소켓을 닫는다. **Room 파사드와 그 메시지 상태는 유지한다** — `connect()`
154
+ * 로 다시 붙으면 같은 파사드가 그대로 이어서 프레임을 받는다.
155
+ *
156
+ * 예전엔 여기서 `rooms.clear()` 를 했는데, 그러면 UI 가 들고 있던 컬렉션이
157
+ * 다음 접근 때 빈 컬렉션으로 교체돼 이미 로드한 히스토리가 사라졌다
158
+ * (React StrictMode 의 connect → disconnect → connect 에서 채팅방이 빈
159
+ * 화면으로 뜨는 증상). 소켓은 재사용되는 `ReconnectingWs` 인스턴스라
160
+ * 파사드가 참조를 유지해도 문제가 없다.
161
+ *
162
+ * 다만 ack 를 기다리던 전송은 응답 받을 길이 없으므로 여기서 실패로
163
+ * 정리한다(낙관적 버블이 영원히 `sending` 으로 남지 않도록).
164
+ */
152
165
  disconnect(): void {
153
166
  this.ws.close();
154
- this.rooms.clear();
167
+ for (const room of this.rooms.values()) room._failPendingSends("disconnected");
155
168
  }
156
169
 
157
170
  room(roomId: string): Room {
@@ -308,6 +308,10 @@ export class MessageCollection {
308
308
  opts?: {
309
309
  replyToId?: string;
310
310
  customType?: string;
311
+ /** 앱 정의 페이로드 원문 — `customType` 과 짝지어 쓰는 테넌트 규약용
312
+ * (예: `customType:"IMAGE"` + `data:{kind,fileId,url,thumbUrl}`).
313
+ * 낙관적 버블에도 그대로 실려 전송 즉시 렌더할 수 있다. */
314
+ data?: Record<string, unknown>;
311
315
  customMeta?: Record<string, unknown>;
312
316
  },
313
317
  ): void {
@@ -319,6 +323,7 @@ export class MessageCollection {
319
323
  content: text,
320
324
  ...(opts?.replyToId ? { replyToId: opts.replyToId } : {}),
321
325
  ...(opts?.customType ? { customType: opts.customType } : {}),
326
+ ...(opts?.data ? { data: opts.data } : {}),
322
327
  ...(opts?.customMeta ? { customMeta: opts.customMeta } : {}),
323
328
  });
324
329
  tempId = sent.tempId;
@@ -1246,6 +1246,17 @@ export class Room {
1246
1246
  this.mySentTempIds.delete(tempId);
1247
1247
  }
1248
1248
 
1249
+ /**
1250
+ * ack 를 기다리던 전송을 전부 실패 처리한다 — `chat.disconnect()` 처럼
1251
+ * 응답이 올 길이 사라졌을 때. 메시지 컬렉션은 건드리지 않는다(낙관적
1252
+ * 버블은 `failed` 로 바뀌어 재전송 UI 를 띄울 수 있다).
1253
+ */
1254
+ _failPendingSends(reason: string): void {
1255
+ for (const tempId of [...this.pendingAcks.keys()]) {
1256
+ this._rejectPending(tempId, new Error(reason));
1257
+ }
1258
+ }
1259
+
1249
1260
  /**
1250
1261
  * Try to re-send a pending frame after a transient server reject
1251
1262
  * (currently only NC-RATE-001 — the tenant's per-second token bucket