@uniai-fe/uds-templates 0.9.0 → 0.9.1
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": "@uniai-fe/uds-templates",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "UNIAI Design System; UI Templates Package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -67,16 +67,16 @@
|
|
|
67
67
|
"react-hook-form": "^7.80.0",
|
|
68
68
|
"sass": "^1.101.0",
|
|
69
69
|
"typescript": "6.0.3",
|
|
70
|
-
"@uniai-fe/eslint-config": "0.3.0",
|
|
71
|
-
"@uniai-fe/react-hooks": "0.2.0",
|
|
72
|
-
"@uniai-fe/next-devkit": "0.4.0",
|
|
73
|
-
"@uniai-fe/tsconfig": "0.2.0",
|
|
74
|
-
"@uniai-fe/uds-foundation": "0.5.0",
|
|
75
70
|
"@uniai-fe/uds-primitives": "0.9.3",
|
|
76
|
-
"@uniai-fe/util-api": "0.2.1",
|
|
77
71
|
"@uniai-fe/util-functions": "0.4.1",
|
|
72
|
+
"@uniai-fe/util-api": "0.2.1",
|
|
73
|
+
"@uniai-fe/uds-foundation": "0.5.0",
|
|
74
|
+
"@uniai-fe/react-hooks": "0.2.0",
|
|
75
|
+
"@uniai-fe/next-devkit": "0.4.0",
|
|
76
|
+
"@uniai-fe/eslint-config": "0.3.1",
|
|
78
77
|
"@uniai-fe/util-jotai": "0.3.0",
|
|
79
78
|
"@uniai-fe/util-next": "0.5.0",
|
|
79
|
+
"@uniai-fe/tsconfig": "0.2.0",
|
|
80
80
|
"@uniai-fe/util-rtc": "0.2.0"
|
|
81
81
|
},
|
|
82
82
|
"scripts": {
|
package/src/cctv/apis/client.ts
CHANGED
|
@@ -41,7 +41,7 @@ class CctvRtcTokenRequestError extends Error {
|
|
|
41
41
|
/**
|
|
42
42
|
* CCTV; RTC token direct response guard
|
|
43
43
|
* @param {unknown} payload 검사할 token response payload
|
|
44
|
-
* @returns {payload is API_Res_CctvRtcToken} direct `{ token }` 응답 여부
|
|
44
|
+
* @returns {payload is API_Res_CctvRtcToken} direct `{ token, session_ref }` 응답 여부
|
|
45
45
|
*/
|
|
46
46
|
const isCctvRtcTokenResponse = (
|
|
47
47
|
payload: unknown,
|
|
@@ -53,7 +53,10 @@ const isCctvRtcTokenResponse = (
|
|
|
53
53
|
const nextPayload = payload as Partial<API_Res_CctvRtcToken>;
|
|
54
54
|
|
|
55
55
|
return (
|
|
56
|
-
typeof nextPayload.token === "string" &&
|
|
56
|
+
typeof nextPayload.token === "string" &&
|
|
57
|
+
nextPayload.token.trim() !== "" &&
|
|
58
|
+
typeof nextPayload.session_ref === "string" &&
|
|
59
|
+
nextPayload.session_ref.trim() !== ""
|
|
57
60
|
);
|
|
58
61
|
};
|
|
59
62
|
|
|
@@ -62,7 +65,8 @@ const isCctvRtcTokenResponse = (
|
|
|
62
65
|
* @param {unknown} payload service token route 응답 payload
|
|
63
66
|
* @returns {API_Res_CctvRtcToken | null} 사용 가능한 token 응답
|
|
64
67
|
* @desc
|
|
65
|
-
* service route가 direct `{ token }` 또는 API base
|
|
68
|
+
* service route가 direct `{ token, session_ref }` 또는 API base
|
|
69
|
+
* `{ data: { token, session_ref } }`를 반환하는 경우를 모두 소비한다.
|
|
66
70
|
*/
|
|
67
71
|
const getCctvRtcTokenResponse = (
|
|
68
72
|
payload: unknown,
|
|
@@ -181,7 +185,7 @@ export const postCctvRtcToken = async ({
|
|
|
181
185
|
|
|
182
186
|
if (!tokenResponse) {
|
|
183
187
|
throw new CctvRtcTokenRequestError(
|
|
184
|
-
"CCTV token response is missing token.",
|
|
188
|
+
"CCTV token response is missing token or session_ref.",
|
|
185
189
|
response.status,
|
|
186
190
|
);
|
|
187
191
|
}
|
|
@@ -234,28 +234,40 @@ export function useCctvRtcStream({
|
|
|
234
234
|
const endpointUsername =
|
|
235
235
|
whepUsername === undefined ? tokenUsername : whepUsername;
|
|
236
236
|
|
|
237
|
-
// 카메라의 RTC 엔드포인트와 사용자명을 조합해
|
|
238
|
-
const
|
|
237
|
+
// 카메라의 RTC 엔드포인트와 사용자명을 조합해 stable WHEP identity endpoint를 계산한다.
|
|
238
|
+
const streamIdentityEndpoint = useMemo(() => {
|
|
239
239
|
// cam_rtc가 없으면 WHEP endpoint를 만들 수 없어 이후 identity/token gate도 닫힌다.
|
|
240
240
|
if (!cam?.cam_rtc) return undefined;
|
|
241
241
|
|
|
242
|
+
// cam_rtc 뒤의 slash 중복을 제거하고 WHEP path를 붙인다.
|
|
243
|
+
const nextEndpoint = new URL(`${cam.cam_rtc.replace(/\/$/, "")}/whep`);
|
|
244
|
+
|
|
242
245
|
// WHEP username override가 있으면 token username과 별개로 endpoint query에만 반영한다.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
+
if (endpointUsername) {
|
|
247
|
+
nextEndpoint.searchParams.set("username", endpointUsername);
|
|
248
|
+
}
|
|
246
249
|
|
|
247
|
-
|
|
248
|
-
return `${cam.cam_rtc.replace(/\/$/, "")}/whep${query}`;
|
|
250
|
+
return nextEndpoint.toString();
|
|
249
251
|
}, [cam?.cam_rtc, endpointUsername]);
|
|
250
252
|
|
|
251
253
|
const streamIdentityKey = useMemo(() => {
|
|
252
254
|
// identity 필수값이 모두 있어야 scheduler와 registry identity cleanup이 가능하다.
|
|
253
|
-
if (
|
|
255
|
+
if (
|
|
256
|
+
!tokenUsername ||
|
|
257
|
+
!cam?.company_id ||
|
|
258
|
+
!cam.cam_id ||
|
|
259
|
+
!streamIdentityEndpoint
|
|
260
|
+
)
|
|
254
261
|
return "";
|
|
255
262
|
|
|
256
263
|
// token 갱신 시각은 제외해 같은 카메라 stream family를 하나의 identity로 묶는다.
|
|
257
|
-
return [
|
|
258
|
-
|
|
264
|
+
return [
|
|
265
|
+
tokenUsername,
|
|
266
|
+
cam.company_id,
|
|
267
|
+
cam.cam_id,
|
|
268
|
+
streamIdentityEndpoint,
|
|
269
|
+
].join("|");
|
|
270
|
+
}, [cam?.cam_id, cam?.company_id, streamIdentityEndpoint, tokenUsername]);
|
|
259
271
|
|
|
260
272
|
// scheduler grant 전에는 token query enabled가 false라 초기 token burst가 발생하지 않는다.
|
|
261
273
|
const streamScheduleStatus = useCctvRtcStreamScheduleStatus({
|
|
@@ -279,9 +291,25 @@ export function useCctvRtcStream({
|
|
|
279
291
|
const isTokenEndpointMissing = isStreamStartGranted && !tokenEndpointUrl;
|
|
280
292
|
const isTokenError = tokenQuery.isError || isTokenEndpointMissing;
|
|
281
293
|
|
|
294
|
+
// token-specific ref는 실제 WHEP 요청 URL에만 붙이고 stream identity/key에서는 제외한다.
|
|
295
|
+
const requestEndpoint = useMemo(() => {
|
|
296
|
+
if (!streamIdentityEndpoint || !tokenQuery.data?.session_ref)
|
|
297
|
+
return undefined;
|
|
298
|
+
|
|
299
|
+
const nextEndpoint = new URL(streamIdentityEndpoint);
|
|
300
|
+
nextEndpoint.searchParams.set("session_ref", tokenQuery.data.session_ref);
|
|
301
|
+
|
|
302
|
+
return nextEndpoint.toString();
|
|
303
|
+
}, [streamIdentityEndpoint, tokenQuery.data?.session_ref]);
|
|
304
|
+
|
|
282
305
|
const streamKeyCandidate = useMemo(() => {
|
|
283
306
|
// streamKey는 online 카메라와 endpoint/token이 모두 준비된 뒤에만 만들 수 있다.
|
|
284
|
-
if (
|
|
307
|
+
if (
|
|
308
|
+
!cam?.cam_id ||
|
|
309
|
+
!cam.cam_online ||
|
|
310
|
+
!streamIdentityEndpoint ||
|
|
311
|
+
!tokenQuery.data?.token
|
|
312
|
+
)
|
|
285
313
|
return "";
|
|
286
314
|
|
|
287
315
|
// token dataUpdatedAt을 포함해 명시 refetch 후에는 새 WHEP 연결 후보로 분리한다.
|
|
@@ -289,14 +317,14 @@ export function useCctvRtcStream({
|
|
|
289
317
|
tokenUsername,
|
|
290
318
|
cam.company_id,
|
|
291
319
|
cam.cam_id,
|
|
292
|
-
|
|
320
|
+
streamIdentityEndpoint,
|
|
293
321
|
tokenQuery.dataUpdatedAt,
|
|
294
322
|
].join("|");
|
|
295
323
|
}, [
|
|
296
324
|
cam?.cam_id,
|
|
297
325
|
cam?.cam_online,
|
|
298
326
|
cam?.company_id,
|
|
299
|
-
|
|
327
|
+
streamIdentityEndpoint,
|
|
300
328
|
tokenQuery.data?.token,
|
|
301
329
|
tokenQuery.dataUpdatedAt,
|
|
302
330
|
tokenUsername,
|
|
@@ -486,7 +514,12 @@ export function useCctvRtcStream({
|
|
|
486
514
|
}
|
|
487
515
|
|
|
488
516
|
// 필수 값이 없으면 스트림을 시작하지 않는다.
|
|
489
|
-
if (
|
|
517
|
+
if (
|
|
518
|
+
!streamKey ||
|
|
519
|
+
!tokenQuery.data?.token ||
|
|
520
|
+
!requestEndpoint ||
|
|
521
|
+
!currentVideo
|
|
522
|
+
)
|
|
490
523
|
return;
|
|
491
524
|
|
|
492
525
|
// 같은 identity에서 token dataUpdatedAt만 바뀌면 새 stream attach 이후 이전 stream을 닫는다.
|
|
@@ -517,7 +550,7 @@ export function useCctvRtcStream({
|
|
|
517
550
|
streamRegistry.start({
|
|
518
551
|
streamKey,
|
|
519
552
|
identityKey: streamIdentityKey,
|
|
520
|
-
endpoint,
|
|
553
|
+
endpoint: requestEndpoint,
|
|
521
554
|
token: tokenQuery.data.token,
|
|
522
555
|
video: currentVideo,
|
|
523
556
|
});
|
|
@@ -569,7 +602,7 @@ export function useCctvRtcStream({
|
|
|
569
602
|
detachVideo();
|
|
570
603
|
};
|
|
571
604
|
}, [
|
|
572
|
-
|
|
605
|
+
requestEndpoint,
|
|
573
606
|
streamIdentityKey,
|
|
574
607
|
streamKey,
|
|
575
608
|
streamRegistry,
|
package/src/cctv/types/api.ts
CHANGED
|
@@ -316,6 +316,7 @@ export interface API_Req_CctvRtcToken {
|
|
|
316
316
|
/**
|
|
317
317
|
* CCTV; 실시간 스트리밍 토큰 응답
|
|
318
318
|
* @property {string} token 인증 토큰
|
|
319
|
+
* @property {string} session_ref WHEP 세션 연계용 단기 참조값
|
|
319
320
|
*/
|
|
320
321
|
export interface API_Res_CctvRtcToken {
|
|
321
322
|
/**
|
|
@@ -323,6 +324,10 @@ export interface API_Res_CctvRtcToken {
|
|
|
323
324
|
* Bearer
|
|
324
325
|
*/
|
|
325
326
|
token: string;
|
|
327
|
+
/**
|
|
328
|
+
* WHEP 요청 세션 연계용 단기 참조값
|
|
329
|
+
*/
|
|
330
|
+
session_ref: string;
|
|
326
331
|
}
|
|
327
332
|
|
|
328
333
|
/**
|