1 | import { useGlobalStore } from './store.js';
|
2 | import { useEventEmitter } from './event-emitter.js';
|
3 | import { STEP_RUN } from './constant.js';
|
4 |
|
5 | const globalStore = useGlobalStore();
|
6 |
|
7 | useEventEmitter().on('change:step', (currentStep) => {
|
8 | if (currentStep === STEP_RUN) {
|
9 | showUserData();
|
10 | }
|
11 | });
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | const localStore = {
|
17 | videoElement: null,
|
18 | buttonGroupElements: {},
|
19 | stepButtonGroup: {
|
20 |
|
21 | loading: [],
|
22 |
|
23 | ready: [],
|
24 |
|
25 | connected: [],
|
26 |
|
27 | recoding: [],
|
28 |
|
29 | afterRecording: []
|
30 | },
|
31 | loading: false,
|
32 |
|
33 | recorder: null,
|
34 |
|
35 | stream: null,
|
36 |
|
37 | recordingData: {
|
38 | imageUri: null,
|
39 | videoUri: null,
|
40 | imageBlob: null,
|
41 | videoBlob: null
|
42 | },
|
43 | collector: null
|
44 | };
|
45 |
|
46 | window.step3Store = localStore;
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | let isInit = false;
|
52 |
|
53 | function init() {
|
54 | if (isInit) {
|
55 | return;
|
56 | }
|
57 | isInit = true;
|
58 | setElements();
|
59 | bindHandler();
|
60 | showUserData();
|
61 | showButtonGroup('loading');
|
62 | createCollector().then(() => {
|
63 | showButtonGroup('ready');
|
64 | });
|
65 | }
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 | function createCollector() {
|
72 | return new Promise((resolve) =>
|
73 | setTimeout(() => {
|
74 | resolve(true);
|
75 | }, 250)
|
76 | ).then(() => {
|
77 |
|
78 | const collectorConfig = {
|
79 | userInfo: {
|
80 | ...globalStore.getUserData()
|
81 | },
|
82 | deviceType: 'pc',
|
83 | webAuth: {
|
84 | userId: 'webAuth_user',
|
85 | token: 'webAuth_token',
|
86 | via: 'webAuth_via'
|
87 | },
|
88 | retryCount: 0,
|
89 | authRenewCount: 0,
|
90 | authRenewBeforeCallback: (webAuth) => {
|
91 | |
92 |
|
93 |
|
94 |
|
95 | const tokenAPI = () => {
|
96 | return new Promise((resolve) => {
|
97 | setTimeout(() => {
|
98 | resolve({ token: 'webAuth_new_token', userId: 'webAuth_new_uid', via: 'new_via' });
|
99 | }, 500);
|
100 | });
|
101 | };
|
102 |
|
103 | return tokenAPI().then((newWebAuth) => {
|
104 | webAuth.token = newWebAuth.token;
|
105 | webAuth.userId = newWebAuth.userId;
|
106 | webAuth.via = newWebAuth.via;
|
107 | });
|
108 | }
|
109 | };
|
110 |
|
111 |
|
112 | globalStore.setCollector(new window.nhnCDSDK.Collector(collectorConfig));
|
113 | const collector = globalStore.getCollector();
|
114 |
|
115 |
|
116 | collector.on('api:success', (data) => {
|
117 | console.log('요청 결과 : ', data);
|
118 | });
|
119 | collector.on('api:fail', ({ resultCode, resultMessage }) => {
|
120 | console.log(`응답 데이터 에러 : ${resultCode} ${resultMessage}`);
|
121 | });
|
122 | collector.on('api:error', (error) => {
|
123 | console.log('요청 실패', error);
|
124 | });
|
125 | });
|
126 | }
|
127 |
|
128 |
|
129 |
|
130 |
|
131 | function setElements() {
|
132 | const buttonConnect = document.querySelector('#uid_camera_connect');
|
133 | const buttonStart = document.querySelector('#uid_record_start');
|
134 | const buttonEnd = document.querySelector('#uid_record_end');
|
135 | const buttonSendVoiceData = document.querySelector('#uid_send_voice');
|
136 | const buttonSendSnapshotData = document.querySelector('#uid_send_snapshot');
|
137 | const buttonReset = document.querySelector('#uid_run_ui_reset');
|
138 | const buttonRevokeToken = document.querySelector('#uid_revoke_token');
|
139 | const configLoadingMsg = document.querySelector('#uid_step3_config_loading');
|
140 |
|
141 | localStore.videoElement = document.querySelector('#uid_local_video');
|
142 | localStore.resultElement = document.querySelector('#uid_record_result');
|
143 | localStore.endTestElement = document.querySelector('#uid_end_test_btn');
|
144 | localStore.buttonGroupElements.connect = buttonConnect;
|
145 | localStore.buttonGroupElements.start = buttonStart;
|
146 | localStore.buttonGroupElements.end = buttonEnd;
|
147 | localStore.buttonGroupElements.sendVoiceData = buttonSendVoiceData;
|
148 | localStore.buttonGroupElements.sendSnapshotData = buttonSendSnapshotData;
|
149 | localStore.buttonGroupElements.reset = buttonReset;
|
150 | localStore.buttonGroupElements.revokeToken = buttonRevokeToken;
|
151 | localStore.buttonGroupElements.configLoadingMsg = configLoadingMsg;
|
152 |
|
153 | localStore.stepButtonGroup.loading = [configLoadingMsg];
|
154 | localStore.stepButtonGroup.ready = [buttonConnect];
|
155 | localStore.stepButtonGroup.connected = [buttonStart];
|
156 | localStore.stepButtonGroup.recoding = [buttonEnd];
|
157 | localStore.stepButtonGroup.afterRecording = [buttonSendVoiceData, buttonSendSnapshotData, buttonReset, buttonRevokeToken];
|
158 | }
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 | function showButtonGroup(groupName) {
|
165 | if (localStore.stepButtonGroup[groupName]) {
|
166 | Object.values(localStore.buttonGroupElements).forEach((button) => {
|
167 | button.style.display = 'none';
|
168 | });
|
169 | Object.values(localStore.stepButtonGroup[groupName]).forEach((button) => {
|
170 | button.style.display = 'inline-block';
|
171 | });
|
172 | }
|
173 | }
|
174 |
|
175 |
|
176 |
|
177 |
|
178 | function bindHandler() {
|
179 |
|
180 | localStore.endTestElement.addEventListener('click', function () {
|
181 | globalStore.getCommunicator().endTest();
|
182 | globalStore.prevStep();
|
183 | });
|
184 |
|
185 | localStore.buttonGroupElements.connect.addEventListener('click', connectHandler);
|
186 |
|
187 | localStore.buttonGroupElements.start.addEventListener('click', recodingHandler);
|
188 |
|
189 | localStore.buttonGroupElements.end.addEventListener('click', afterRecodingHandler);
|
190 |
|
191 | localStore.buttonGroupElements.sendVoiceData.addEventListener('click', sendVoiceHandler);
|
192 |
|
193 | localStore.buttonGroupElements.sendSnapshotData.addEventListener('click', sendSnapshotHandler);
|
194 |
|
195 | localStore.buttonGroupElements.reset.addEventListener('click', resetHandler);
|
196 |
|
197 | localStore.buttonGroupElements.revokeToken.addEventListener('click', revokeAccessTokenHandler);
|
198 | }
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 | function renderAPIResult(apiName, data) {
|
206 | const container = document.querySelector('#uid_step_run_api_result');
|
207 | let renderData = '';
|
208 | if (data) {
|
209 | renderData = JSON.stringify(data, null, ' ');
|
210 | }
|
211 | let html = '';
|
212 | if (apiName) {
|
213 | html = `<h4>${apiName}</h4>`;
|
214 | }
|
215 | container.innerHTML = `${html}${renderData}`;
|
216 | }
|
217 |
|
218 |
|
219 |
|
220 |
|
221 | function connectHandler() {
|
222 | window.navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' }, audio: true }).then((stream) => {
|
223 | localStore.stream = stream;
|
224 | localStore.videoElement.srcObject = stream;
|
225 | showButtonGroup('connected');
|
226 | });
|
227 | }
|
228 |
|
229 |
|
230 |
|
231 |
|
232 | function recodingHandler() {
|
233 | if (!localStore.recorder) {
|
234 | localStore.recorder = window.RecordRTC(localStore.stream, {
|
235 | type: 'audio',
|
236 | mimeType: 'audio/wav',
|
237 | disableLogs: true
|
238 | });
|
239 | }
|
240 | localStore.resultElement.innerHTML = '';
|
241 | localStore.recorder.startRecording();
|
242 | showButtonGroup('recoding');
|
243 | }
|
244 |
|
245 |
|
246 |
|
247 |
|
248 | function afterRecodingHandler() {
|
249 | const { recorder } = localStore;
|
250 | recorder.stopRecording(() => {
|
251 | const blob = recorder.getBlob();
|
252 | if (blob) {
|
253 | const URL = window.webkitURL || window.URL;
|
254 | const videoUri = URL.createObjectURL(blob);
|
255 | const canvas = createSnapshotCanvas();
|
256 | const imageUri = canvas.toDataURL('image/jpg');
|
257 |
|
258 | canvas.toBlob((canvasBlob) => {
|
259 | localStore.recordingData = {
|
260 | imageUri: imageUri,
|
261 | videoUri: videoUri,
|
262 | videoBlob: blob,
|
263 | imageBlob: canvasBlob
|
264 | };
|
265 |
|
266 | renderResult();
|
267 | showButtonGroup('afterRecording');
|
268 | });
|
269 | }
|
270 | });
|
271 | }
|
272 |
|
273 |
|
274 |
|
275 |
|
276 | function renderResult() {
|
277 | const { imageUri, videoUri } = localStore.recordingData;
|
278 | localStore.resultElement.innerHTML = `
|
279 | <h3>레코딩 결과</h3>
|
280 | <img src="${imageUri}" width="100" height="80" alt="snapshot" />
|
281 | <video src="${videoUri}" controls width="300" height="80" />
|
282 | `;
|
283 | }
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 | function createSnapshotCanvas() {
|
290 | const canvas = document.createElement('canvas');
|
291 | canvas.width = 640;
|
292 | canvas.height = 480;
|
293 | const ctx = canvas.getContext('2d');
|
294 | ctx.drawImage(localStore.videoElement, 0, 0, canvas.width, canvas.height);
|
295 | return canvas;
|
296 | }
|
297 |
|
298 |
|
299 |
|
300 |
|
301 | function sendVoiceHandler() {
|
302 | const collector = globalStore.getCollector();
|
303 | if (!collector) {
|
304 | console.error('collector 인스턴스가 존재하지 않음');
|
305 | return;
|
306 | }
|
307 | const { imageBlob, videoBlob } = localStore.recordingData;
|
308 | if (!imageBlob || !videoBlob) {
|
309 | console.error('저장된 데이터 없음');
|
310 | return;
|
311 | }
|
312 |
|
313 | const formData = new FormData();
|
314 | formData.set(
|
315 | 'file',
|
316 | new File([videoBlob], 'cam-voice.wav', {
|
317 | type: 'audio/wav'
|
318 | })
|
319 | );
|
320 | const query = { reqTime: Math.floor(Date.now() / 1000) };
|
321 |
|
322 |
|
323 | collector
|
324 | .fetchVoiceDetect(formData, query)
|
325 | .then((res) => {
|
326 | renderAPIResult('음성 데이터 전송', res);
|
327 | })
|
328 | .catch((err) => {
|
329 | const { stack, message } = err;
|
330 | let result = { message, stack };
|
331 | if (err?.header?.isSuccessful === false) {
|
332 | result = { ...err };
|
333 | }
|
334 | renderAPIResult('음성 데이터 전송', result);
|
335 | });
|
336 | }
|
337 |
|
338 |
|
339 |
|
340 |
|
341 | function sendSnapshotHandler() {
|
342 | const collector = globalStore.getCollector();
|
343 | if (!collector) {
|
344 | console.error('collector 인스턴스가 존재하지 않음');
|
345 | return;
|
346 | }
|
347 | const { imageBlob, videoBlob } = localStore.recordingData;
|
348 | if (!imageBlob || !videoBlob) {
|
349 | console.error('저장된 데이터 없음');
|
350 | return;
|
351 | }
|
352 |
|
353 | const data = new FormData();
|
354 | data.append('file', new File([imageBlob], 'cam-snapshot.jpg', { type: 'image/jpeg' }));
|
355 | const query = {
|
356 | reqTime: Math.floor(Date.now() / 1000),
|
357 | camLocation: 'front'
|
358 | };
|
359 |
|
360 |
|
361 | collector
|
362 | .fetchBehaviorDetect(data, query)
|
363 | .then((res) => {
|
364 | renderAPIResult('스냅샷 데이터 전송', res);
|
365 | })
|
366 | .catch((err) => {
|
367 | const { stack, message } = err;
|
368 | let result = { message, stack };
|
369 | if (err?.header?.isSuccessful === false) {
|
370 | result = { ...err };
|
371 | }
|
372 | renderAPIResult('스냅샷 데이터 전송', result);
|
373 | });
|
374 | }
|
375 |
|
376 |
|
377 |
|
378 |
|
379 | function resetHandler() {
|
380 | localStore.recordingData = {
|
381 | imageUri: null,
|
382 | videoUri: null,
|
383 | imageBlob: null,
|
384 | videoBlob: null
|
385 | };
|
386 | localStore.resultElement.innerHTML = '';
|
387 | localStore.videoElement.srcObject = null;
|
388 | showButtonGroup('ready');
|
389 | renderAPIResult(null);
|
390 | }
|
391 |
|
392 |
|
393 |
|
394 |
|
395 | function revokeAccessTokenHandler() {
|
396 | const collector = globalStore.getCollector();
|
397 | if (!collector) {
|
398 | console.error('collector 인스턴스가 존재하지 않음');
|
399 | return;
|
400 | }
|
401 |
|
402 | collector
|
403 | .revokeAccessToken()
|
404 | .then((res) => {
|
405 | renderAPIResult('access token 초기화', res);
|
406 | })
|
407 | .catch((err) => {
|
408 | const { stack, message } = err;
|
409 | let result = { message, stack };
|
410 | if (err?.header?.isSuccessful === false) {
|
411 | result = { ...err };
|
412 | }
|
413 | renderAPIResult('access token 초기화', result);
|
414 | });
|
415 | }
|
416 |
|
417 |
|
418 |
|
419 |
|
420 | function showUserData() {
|
421 | document.querySelector('#uid_user_info_preview_step3').innerHTML = JSON.stringify(globalStore.getUserData());
|
422 | }
|
423 |
|
424 | export default {
|
425 | init
|
426 | };
|