@vibexnpm/talkx 3.2.0 → 3.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibexnpm/talkx",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "type": "module",
5
5
  "description": "TalkFlow SDK - Chat & WebRTC unified client",
6
6
  "main": "dist/talkflow-sdk.umd.js",
@@ -18,6 +18,14 @@ const MAX_FILES_PER_MESSAGE = 20;
18
18
  */
19
19
  const MAX_ROOM_PM_PROMPT_LENGTH = 20000;
20
20
 
21
+ /**
22
+ * 매직 시그니처가 없는 평문 문서 MIME (txt / md).
23
+ * 서버 도메인 상수(PlainTextDocumentType)의 미러 — 서버가 평문 타입을 늘리면 여기도 함께 늘려야 한다.
24
+ * 빠지면 이 MIME 들이 DOCUMENT 가 아닌 FILE 로 분류돼 그룹 수 예측이 서버와 어긋나고,
25
+ * 결과적으로 다중 파일 전송의 messageId prediction 이 틀어진다.
26
+ */
27
+ const PLAIN_TEXT_DOCUMENT_MIME_TYPES = new Set(['text/plain', 'text/markdown', 'text/x-markdown']);
28
+
21
29
  class ChatClient extends EventEmitter {
22
30
  /**
23
31
  * @param {Object} options
@@ -1787,6 +1795,21 @@ class ChatClient extends EventEmitter {
1787
1795
  return typeof message === 'string' && message.trim().length > 0;
1788
1796
  }
1789
1797
 
1798
+ /**
1799
+ * Content-Type 의 base MIME — {@code ;} 이후 파라미터(charset 등) 제거 + trim + 소문자.
1800
+ * <p>서버 {@code ChatFileCategory.baseMimeType} mirror. 'text/plain; charset=utf-8' → 'text/plain'.
1801
+ * 'text/plain-invalid' 같은 변형은 그대로 남아 정확 비교에서 걸러진다.</p>
1802
+ *
1803
+ * @private
1804
+ * @param {string} mimeType
1805
+ * @returns {string}
1806
+ */
1807
+ _baseMimeType(mimeType) {
1808
+ const raw = mimeType || '';
1809
+ const sep = raw.indexOf(';');
1810
+ return (sep >= 0 ? raw.slice(0, sep) : raw).trim().toLowerCase();
1811
+ }
1812
+
1790
1813
  /**
1791
1814
  * MIME 타입 → 서버 {@code ChatMessageType} 분류 (서버 {@code FileGroupingService.classifyFileType} mirror).
1792
1815
  * <p>서버 도메인 규칙과 동일해야 group count 예측이 일치한다. 서버 변경 시 본 메서드도 동기화 필요.</p>
@@ -1796,11 +1819,15 @@ class ChatClient extends EventEmitter {
1796
1819
  * @returns {string} 'IMAGE' | 'VIDEO' | 'AUDIO' | 'DOCUMENT' | 'FILE'
1797
1820
  */
1798
1821
  _classifyFileType(mimeType) {
1799
- const mime = (mimeType || '').toLowerCase();
1822
+ const mime = this._baseMimeType(mimeType);
1800
1823
  if (mime.startsWith('image/')) return 'IMAGE';
1801
1824
  if (mime.startsWith('video/')) return 'VIDEO';
1802
1825
  if (mime.startsWith('audio/')) return 'AUDIO';
1803
- if (mime.includes('pdf') || mime.includes('document')) return 'DOCUMENT';
1826
+ // 평문(txt/md)도 DOCUMENT — 서버가 온디맨드 번역·PM 문서 인식 대상으로 삼는 기준과 동일해야 한다
1827
+ if (mime.includes('pdf') || mime.includes('document')
1828
+ || PLAIN_TEXT_DOCUMENT_MIME_TYPES.has(mime)) {
1829
+ return 'DOCUMENT';
1830
+ }
1804
1831
  return 'FILE';
1805
1832
  }
1806
1833
 
@@ -411,9 +411,20 @@ class PushManager {
411
411
 
412
412
  // 알림 권한 요청
413
413
  // - 'default' 상태: 브라우저가 권한창을 표시하고 사용자 응답을 대기
414
- // - 'granted' 상태: 즉시 'granted' 반환 (창 안 뜸)
414
+ // - 'granted' 상태: 재요청하지 않고 즉시 통과 (아래 주의사항 참고)
415
415
  // - 'denied' 상태: 위 가드에서 이미 throw 했으므로 여기 도달 불가
416
- const permission = await Notification.requestPermission();
416
+ //
417
+ // !! Safari/WebKit 주의 !!
418
+ // Chromium 은 이미 granted 인 상태에서 requestPermission() 을 호출하면 창 없이 즉시
419
+ // 'granted' 를 반환하지만, Safari/WebKit 은 이 API 를 '사용자 제스처 안에서만' 허용한다.
420
+ // 따라서 연결 직후 자동 활성화처럼 제스처가 없는 경로에서는 이미 허용된 상태여도 거부로 반환되어
421
+ // PERMISSION_DENIED 로 throw 되고, 그 아래의 토큰 발급/서버 등록/포그라운드 onMessage 등록까지
422
+ // 모두 건너뛰게 된다(iOS 홈 화면 앱에서 새로고침마다 권한 차단 배너 + 포그라운드 메시지 미수신).
423
+ // 이미 허용된 상태에서는 재요청 자체가 불필요하므로 호출을 건너뛴다.
424
+ const permission =
425
+ Notification.permission === 'granted'
426
+ ? 'granted'
427
+ : await Notification.requestPermission();
417
428
  if (permission !== 'granted') {
418
429
  throw new PushError(
419
430
  PushErrorCode.PERMISSION_DENIED,