@vibexnpm/talkx 8.13.0 → 9.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/README.md +3 -2
- package/dist/index.d.ts +693 -155
- package/dist/talkflow-sdk.esm.js +1 -1
- package/dist/talkflow-sdk.esm.js.map +1 -1
- package/dist/talkflow-sdk.standalone.js +1 -1
- package/dist/talkflow-sdk.standalone.js.map +1 -1
- package/dist/talkflow-sdk.umd.js +1 -1
- package/dist/talkflow-sdk.umd.js.map +1 -1
- package/package.json +4 -2
- package/src/TalkFlowClient.js +13 -5
- package/src/chat/ChatClient.js +155 -0
- package/src/constants.js +39 -41
- package/src/core/ConnectionManager.js +11 -1
- package/src/index.js +4 -4
- package/src/talkflow/delegates.js +19 -23
- package/src/talkflow/eventForwarding.js +11 -23
- package/src/talkflow/session.js +37 -4
- package/src/utils/ApiClient.js +45 -41
- package/src/webrtc/CallLedger.js +135 -0
- package/src/webrtc/LiveKitMediaSession.js +1568 -0
- package/src/webrtc/WebRTCClient.js +909 -1068
- package/types/__typetests__/call.types.ts +101 -0
- package/types/__typetests__/scheduledReports.types.ts +138 -0
- package/types/index.d.ts +693 -155
- package/src/webrtc/MediaStreamManager.js +0 -478
- package/src/webrtc/PeerConnectionManager.js +0 -476
package/src/utils/ApiClient.js
CHANGED
|
@@ -104,58 +104,62 @@ class ApiClient {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
//
|
|
107
|
+
// 시간 상한은 본문을 다 읽을 때까지다 — 헤더만 오고 본문이 멈춰도 끝나게(AuthSession._request 와 같다). 끊으면 본문 읽기도 AbortError 로
|
|
108
|
+
// 끝난다.
|
|
108
109
|
const controller = new AbortController();
|
|
109
110
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
110
111
|
|
|
111
|
-
let response;
|
|
112
112
|
try {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
throw
|
|
113
|
+
let response;
|
|
114
|
+
try {
|
|
115
|
+
this.logger.debug(`${method} ${url}`);
|
|
116
|
+
|
|
117
|
+
response = await fetch(url, {
|
|
118
|
+
method,
|
|
119
|
+
headers: { ...this._getHeaders(), ...headers },
|
|
120
|
+
credentials: this.authProvider ? 'omit' : 'same-origin',
|
|
121
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
122
|
+
signal: controller.signal
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (error.name !== 'AbortError') {
|
|
126
|
+
this.logger.error(`API Error: ${method} ${url}`, error);
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
clearTimeout(timeoutId);
|
|
131
|
+
// 응답 파싱
|
|
132
|
+
const contentType = response.headers.get('content-type');
|
|
133
|
+
let data;
|
|
136
134
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
if (contentType && contentType.includes('application/json')) {
|
|
136
|
+
data = await response.json();
|
|
137
|
+
} else {
|
|
138
|
+
data = await response.text();
|
|
139
|
+
}
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
141
|
+
// 에러 처리
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
const error = new Error(data?.message || `HTTP ${response.status}`);
|
|
144
|
+
error.code = ErrorTypes.API_ERROR;
|
|
145
|
+
error.status = response.status;
|
|
146
|
+
error.response = data;
|
|
147
|
+
this.logger.error(`API Error: ${method} ${url}`, error);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
146
150
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
error
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
this.logger.debug(`Response ${response.status}`);
|
|
152
|
+
return data;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error?.name === 'AbortError') {
|
|
155
|
+
const timeoutError = new Error('Request timeout');
|
|
156
|
+
timeoutError.code = ErrorTypes.API_TIMEOUT;
|
|
157
|
+
throw timeoutError;
|
|
158
|
+
}
|
|
154
159
|
throw error;
|
|
160
|
+
} finally {
|
|
161
|
+
clearTimeout(timeoutId);
|
|
155
162
|
}
|
|
156
|
-
|
|
157
|
-
this.logger.debug(`Response ${response.status}`);
|
|
158
|
-
return data;
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
// HTTP 메서드 헬퍼
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CallLedger
|
|
3
|
+
* 통화 이벤트 장부 — 브라우저·LiveKit 없이 판정만 한다(화상통화 SFU 설계 v0.7 I8·§5.3).
|
|
4
|
+
*
|
|
5
|
+
* <ul>
|
|
6
|
+
* <li>중복 전달은 버린다 — 같은 (version, 종류) 는 한 번만.</li>
|
|
7
|
+
* <li>«지금 상태» 를 뜻하는 이벤트(초대)는 본 것보다 낡은 version 이면 버린다 — 통화가 이미 그 뒤로 나아갔다. 거절·응답·취소·종료처럼
|
|
8
|
+
* «일어난 일» 을 알리는 이벤트는 늦게 와도 버리지 않는다(두 사람의 거절이 역순으로 와도 둘 다 방장에게 간다).</li>
|
|
9
|
+
* <li>서버가 끝냈다고 알린 통화는 묘비로 기억한다 — 늦게 온 초대가 다시 울리지 않게. 이 기기만 나간 것(나가기·재접속 실패·다른 기기로
|
|
10
|
+
* 옮김)은 묘비가 아니다 — 다시 들어갈 수 있다.</li>
|
|
11
|
+
* <li>울림 정지 표식은 version 과 따로 둔다 — «응답함»·«취소» 를 한 번 받으면 그 통화의 울림을 되살리지 않는다.</li>
|
|
12
|
+
* </ul>
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** 기억하는 통화 수 상한 — 오래된 것부터 잊는다(초대·종료는 짧게만 의미가 있다). */
|
|
16
|
+
const DEFAULT_MAX_CALLS = 64;
|
|
17
|
+
/** 통화 하나에서 기억하는 (version, 종류) 수 상한. */
|
|
18
|
+
const MAX_SEEN_PER_CALL = 64;
|
|
19
|
+
|
|
20
|
+
class CallLedger {
|
|
21
|
+
/**
|
|
22
|
+
* @param {Object} [options]
|
|
23
|
+
* @param {number} [options.maxCalls=64]
|
|
24
|
+
*/
|
|
25
|
+
constructor({ maxCalls = DEFAULT_MAX_CALLS } = {}) {
|
|
26
|
+
this._maxCalls = maxCalls;
|
|
27
|
+
/** @type {Map<string, {version: number|null, seen: Set<string>, ended: {reason: string}|null, ringStopped: boolean}>} */
|
|
28
|
+
this._calls = new Map();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 본 version 보다 낡았나 — 초대처럼 «지금 상태» 를 뜻하는 이벤트에만 쓴다.
|
|
33
|
+
* @param {string} callRoomId
|
|
34
|
+
* @param {number} version
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
*/
|
|
37
|
+
isStale(callRoomId, version) {
|
|
38
|
+
const entry = this._calls.get(callRoomId);
|
|
39
|
+
return Boolean(entry && Number.isFinite(version) && entry.version !== null && version < entry.version);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 이벤트를 기억한다 — 처음 보는 (version, 종류) 면 true, 이미 본 것(중복 전달)이면 false. 최신 version 을 올린다.
|
|
44
|
+
* @param {string} callRoomId
|
|
45
|
+
* @param {number} version
|
|
46
|
+
* @param {string} type
|
|
47
|
+
* @returns {boolean}
|
|
48
|
+
*/
|
|
49
|
+
record(callRoomId, version, type) {
|
|
50
|
+
const entry = this._entry(callRoomId);
|
|
51
|
+
if (!Number.isFinite(version)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
const key = `${version}:${type}`;
|
|
55
|
+
if (entry.seen.has(key)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
entry.seen.add(key);
|
|
59
|
+
if (entry.seen.size > MAX_SEEN_PER_CALL) {
|
|
60
|
+
entry.seen.delete(entry.seen.values().next().value);
|
|
61
|
+
}
|
|
62
|
+
if (entry.version === null || version > entry.version) {
|
|
63
|
+
entry.version = version;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* REST 응답 등에서 본 version 을 바닥으로 올린다 — 그보다 낡은 초대는 이후 버린다.
|
|
70
|
+
* @param {string} callRoomId
|
|
71
|
+
* @param {number} version
|
|
72
|
+
*/
|
|
73
|
+
observe(callRoomId, version) {
|
|
74
|
+
if (!Number.isFinite(version)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const entry = this._entry(callRoomId);
|
|
78
|
+
if (entry.version === null || version > entry.version) {
|
|
79
|
+
entry.version = version;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 서버가 끝냈다고 알린 통화로 기억한다(묘비). 처음 사유가 남는다.
|
|
85
|
+
* @param {string} callRoomId
|
|
86
|
+
* @param {string} reason
|
|
87
|
+
*/
|
|
88
|
+
markEnded(callRoomId, reason) {
|
|
89
|
+
const entry = this._entry(callRoomId);
|
|
90
|
+
if (!entry.ended) {
|
|
91
|
+
entry.ended = { reason };
|
|
92
|
+
}
|
|
93
|
+
entry.ringStopped = true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {string} callRoomId
|
|
98
|
+
* @returns {string|null} 서버에서 끝났으면 그 사유, 아니면 null
|
|
99
|
+
*/
|
|
100
|
+
endedReason(callRoomId) {
|
|
101
|
+
return this._calls.get(callRoomId)?.ended?.reason ?? null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** @param {string} callRoomId */
|
|
105
|
+
stopRinging(callRoomId) {
|
|
106
|
+
this._entry(callRoomId).ringStopped = true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {string} callRoomId
|
|
111
|
+
* @returns {boolean} 울림을 되살리면 안 되는가(정지 표식 또는 묘비)
|
|
112
|
+
*/
|
|
113
|
+
isRingStopped(callRoomId) {
|
|
114
|
+
const entry = this._calls.get(callRoomId);
|
|
115
|
+
return Boolean(entry && (entry.ringStopped || entry.ended));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_entry(callRoomId) {
|
|
119
|
+
let entry = this._calls.get(callRoomId);
|
|
120
|
+
if (entry) {
|
|
121
|
+
// 최근에 쓴 통화를 뒤로 옮겨 오래된 것부터 잊게 한다.
|
|
122
|
+
this._calls.delete(callRoomId);
|
|
123
|
+
this._calls.set(callRoomId, entry);
|
|
124
|
+
return entry;
|
|
125
|
+
}
|
|
126
|
+
entry = { version: null, seen: new Set(), ended: null, ringStopped: false };
|
|
127
|
+
this._calls.set(callRoomId, entry);
|
|
128
|
+
while (this._calls.size > this._maxCalls) {
|
|
129
|
+
this._calls.delete(this._calls.keys().next().value);
|
|
130
|
+
}
|
|
131
|
+
return entry;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default CallLedger;
|