1 | # NHN Proctor SDK
|
2 |
|
3 | # Installing
|
4 |
|
5 | Using npm:
|
6 |
|
7 | ```shell
|
8 | $ npm install nhn-cd-sdk
|
9 | ```
|
10 |
|
11 | Using jsDelivr CDN:
|
12 |
|
13 | ```html
|
14 | <script src="https://cdn.jsdelivr.net/npm/nhn-cd-sdk@{{VERSION}}/dist/nhn-cd-sdk.js"></script>
|
15 | ```
|
16 |
|
17 | Using unpkg CDN:
|
18 |
|
19 | ```html
|
20 | <script src="https://unpkg.com/nhn-cd-sdk@{{VERSION}}/dist/nhn-cd-sdk.js"></script>
|
21 | ```
|
22 |
|
23 | # Usage
|
24 |
|
25 | `import` usage:
|
26 |
|
27 | ```js
|
28 | import { Launcher, Collector, Communicator } from 'nhn-cd-sdk';
|
29 |
|
30 | const config = {
|
31 | /* ... */
|
32 | };
|
33 | const collector = new Collector(config);
|
34 | ```
|
35 |
|
36 | `CDN` usage:
|
37 |
|
38 | ```js
|
39 | const config = {
|
40 | /* ... */
|
41 | };
|
42 | const collector = new window.nhnCDSDK.Collector(config);
|
43 | ```
|
44 |
|
45 | # Run example
|
46 |
|
47 | Using npm script:
|
48 |
|
49 | ```shell
|
50 | $ npm i
|
51 | $ npm run example
|
52 | ```
|
53 |
|
54 | Using example file:
|
55 |
|
56 | Copy the code from the `examples` folder to your local project at [jsdelivr](https://cdn.jsdelivr.net/npm/nhn-cd-sdk@latest/examples/) or [unpkg](https://unpkg.com/browse/nhn-cd-sdk@latest/examples/).
|
57 | Then change the code below in the html file as follows.
|
58 |
|
59 | ```html
|
60 | <!-- before -->
|
61 | <script src="/dist/nhn-cd-sdk.js"></script>
|
62 |
|
63 | <!-- after -->
|
64 | <!-- using jsdelivr -->
|
65 | <script src="https://cdn.jsdelivr.net/npm/nhn-cd-sdk@{{VERSION}}/dist/nhn-cd-sdk.js"></script>
|
66 | <!-- using unpkg -->
|
67 | <script src="https://unpkg.com/nhn-cd-sdk@{{VERSION}}/dist/nhn-cd-sdk.js"></script>
|
68 | ```
|
69 |
|
70 | # Launcher
|
71 |
|
72 | URI Scheme를 사용한 Proctor 앱 실행
|
73 |
|
74 | ## Creating an Launcher instance
|
75 |
|
76 | ### new Launcher([serviceUrl[,uriParameters]])
|
77 |
|
78 | - serviceUrl
|
79 | - Custom URI Scheme을 통해 이동하는 고객사의 서비스 URL.(URL encoding 후 전달)
|
80 | - uriParameters
|
81 | - URI Scheme을 통해 기능 수행 시 추가로 전달할 정보를 정의
|
82 |
|
83 | #### Example
|
84 |
|
85 | ```js
|
86 | const launcher = new Launcher('https://...', { option1: 'value' });
|
87 | ```
|
88 |
|
89 | ## instance methods
|
90 |
|
91 | ### launcher#setServiceUrl(serviceUrl: String)
|
92 |
|
93 | - 서비스 URL 설정 또는 기존의 서비스 URL 값을 변경
|
94 |
|
95 | ### launcher#setUriParams(uriParameters: Object)
|
96 |
|
97 | - 옵션 설정 또는 기존의 옵션 값을 변경
|
98 |
|
99 | ### launcher#launch()
|
100 |
|
101 | - Proctor 실행
|
102 |
|
103 | <br/>
|
104 |
|
105 | # Communicator
|
106 |
|
107 | - 시험 시작, 시험 종료 제어
|
108 |
|
109 | ## Get an Communicator instance
|
110 |
|
111 | ### Example
|
112 |
|
113 | ```js
|
114 | const communicator = new window.nhnCDSDK.Communicator();
|
115 | ```
|
116 |
|
117 | ## instance methods
|
118 |
|
119 | ### communicator#initialize(config: Object)
|
120 |
|
121 | - NHN Proctor 사용자 정보 초기화
|
122 |
|
123 | ### Example
|
124 |
|
125 | ```js
|
126 | communicator.initialize({
|
127 | appKey: '{{APP_KEY}}',
|
128 | examNo: '{{EXAM_NO}}',
|
129 | userId: '{{USER_ID}}'
|
130 | });
|
131 | ```
|
132 |
|
133 | ### communicator#communicate(event[,properties])
|
134 |
|
135 | - NHN Proctor 이벤트 전달
|
136 |
|
137 | ### Example
|
138 |
|
139 | ```js
|
140 | event = '{{EVENT_NAME}}'; // or 'beginTest' or 'endTest'
|
141 | properties = {
|
142 | key: '{{VALUE}}'
|
143 | };
|
144 | ```
|
145 |
|
146 | ### communicator#beginTest()
|
147 |
|
148 | - 시험 시작
|
149 |
|
150 | ### communicator#endTest()
|
151 |
|
152 | - 시험 종료
|
153 |
|
154 | # Collector
|
155 |
|
156 | - Collector API 제공
|
157 |
|
158 | ## Get an Collector instance
|
159 |
|
160 | ### Example
|
161 |
|
162 | ```js
|
163 | const collector = new window.nhnCDSDK.Collector(config);
|
164 | const config = {
|
165 | userInfo: {
|
166 | appKey: '{{APP_KEY}}', // 통합 AppKey 또는 서비스 AppKey
|
167 | examNo: '{{EXAM_NO}}', // 시험 번호
|
168 | userId: '{{USER_ID}}' // 사용자 아이디
|
169 | },
|
170 | deviceType: 'pc',
|
171 | webAuth: {
|
172 | userId: '{{WEB_AUTH_USER_ID}}', // 사용자 ID(수험생 번호)
|
173 | token: '{{WEB_AUTH_TOKEN}}', // 고객사에서 발급한 WebAuth 인증 토큰
|
174 | via: '{{WEB_AUTH_VIA}}' // 기타 정보
|
175 | },
|
176 | retryCount: '{{RETRY_COUNT}}',
|
177 | authRenewCount: '{{AUTH_RENEW_COUNT',
|
178 | authRenewBeforeCallback: (webAuth) => {
|
179 | // use your token api.
|
180 | const tokenAPI = () => {
|
181 | return new Promise((resolve) => {
|
182 | setTimeout(() => {
|
183 | resolve({ token: '{{NEW_WEB_AUTH_TOKEN}}', userId: '{{NEW_WEB_AUTH_USER_ID}}', via: 'NEW_WEB_AUTH_VIA' });
|
184 | }, 500);
|
185 | });
|
186 | };
|
187 |
|
188 | return tokenAPI().then((newWebAuth) => {
|
189 | webAuth.token = newWebAuth.token;
|
190 | webAuth.userId = newWebAuth.userId;
|
191 | webAuth.via = newWebAuth.via;
|
192 | });
|
193 | }
|
194 | };
|
195 | ```
|
196 |
|
197 | | property | type | description | optional |
|
198 | | :---------------------: | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | :------: |
|
199 | | userInfo | String | 사용자 정보 | O |
|
200 | | userInfo.appKey | String | 통합 AppKey 또는 서비스 AppKey | O |
|
201 | | userInfo.examNo | String | 시험 번호 | O |
|
202 | | userInfo.userId | String | 사용자 ID(수험생 번호) | O |
|
203 | | deviceType | String | 장비 구분(pc: PC, mo: Mobile ) | O |
|
204 | | webAuth | JSON | WebAuth 인증 데이터 | O |
|
205 | | webAuth.userId | String | 사용자 ID(수험생 번호) | O |
|
206 | | webAuth.token | String | 고객사에서 발급한 WebAuth 인증 토큰 | O |
|
207 | | webAuth.via | String | 기타 정보 | X |
|
208 | | apiBaseUrl | String | api base url | O |
|
209 | | retryCount | Number | api 요청 실패시 재요청 횟수 | O |
|
210 | | authRenewCount | Number | 인증 갱신 실패시 재요청 횟수 | O |
|
211 | | authRenewBeforeCallback | Function | WebAuth 인증 데이터를 갱신시키기 위한 콜백 함수<br />Promise를 반환해야 한다.<br />반환된 Promise는 새로운 인증에 필요한 `webAuth` 정보를 반환해한다. | O |
|
212 |
|
213 | ## instance methods
|
214 |
|
215 | ### collector#getApiBaseUrl():
|
216 |
|
217 | - api base url 반환
|
218 |
|
219 | ### collector#setApiBaseUrl(apiBaseUrl: string)
|
220 |
|
221 | - api base url 변경
|
222 |
|
223 | ### collector#updateUserInfo(newUserInfo: Object)
|
224 |
|
225 | - 사용자 정보 갱신
|
226 | - 3개의 항목(appKey, examNo, userId) 중 최소 1개 이상의 key-value 쌍을 전달
|
227 |
|
228 | ### Example
|
229 |
|
230 | ```js
|
231 | collector.updateUserInfo({
|
232 | appKey: '{{UPDATE_NEW_APP_KEY}}', // 통합 AppKey 또는 서비스 AppKey
|
233 | examNo: '{{UPDATE_NEW_EXAM_NO}}', // 시험 번호
|
234 | userId: '{{UPDATE_NEW_USER_ID}}' // 사용자 아이디
|
235 | });
|
236 | ```
|
237 |
|
238 | ### collector#setRetryCount(count: Number)
|
239 |
|
240 | - API 요청 실패 시 재요청 횟수 변경
|
241 |
|
242 | ### collector#setAuthRenewCount(count: Number)
|
243 |
|
244 | - 인증 갱신 실패 시 재요청 횟수 변경
|
245 |
|
246 | ### collector#setAuthRenewBeforeCallback(callback: Function)
|
247 |
|
248 | - WebAuth 인증 데이터를 갱신시키기 위한 콜백 함수
|
249 | - Promise를 반환한다.
|
250 | - 일부 WebAuth 값을 반환하면 반환된 필드가 갱신된다.
|
251 |
|
252 | ### on(event: String, listener: Function)
|
253 |
|
254 | - 요청에 대한 성공, 실패 구독 등록
|
255 |
|
256 | ```js
|
257 | collector.on('api:success', (response) => {
|
258 | console.log('요청 결과 : ', response);
|
259 | });
|
260 | collector.on('api:fail', ({ resultCode, resultMessage }) => {
|
261 | console.log(`응답 데이터 에러 : ${resultCode} ${resultMessage}`);
|
262 | });
|
263 | ```
|
264 |
|
265 | ### collector#revokeAccessToken()
|
266 |
|
267 | - 발급 받은 Token을 취소(강제 만료 시키기 위한) API
|
268 |
|
269 | #### Example
|
270 |
|
271 | ```js
|
272 | collector#revokeAccessToken()
|
273 |
|
274 | ```
|
275 |
|
276 | ### collector#fetchFaceDetect(bodyData: {image: { url: string, bytes: Array<Uint8Array> }})
|
277 |
|
278 | - 얼굴 인식 API 요청
|
279 |
|
280 | #### [Request Body]
|
281 |
|
282 | | 이름 | 타입 | 설명 | 필수 여부 |
|
283 | | ----------- | ------ | ------------------------------------------------------------------------------------------ | ----------- |
|
284 | | image.url | String | 이미지의 URL<br />image.url, image.bytes 중 반드시 1개만 있어야 한다. | 선택적 필수 |
|
285 | | image.bytes | Blob | base64로 인코딩된 이미지 바이트<br />image.url, image.bytes 중 반드시 1개만 존재해야 한다. | 선택적 필수 |
|
286 |
|
287 | #### Example
|
288 |
|
289 | ```js
|
290 | const input = document.querySelector('#uid_face_detect_input');
|
291 | const [file] = input.files;
|
292 | file.arrayBuffer().then((buffer) => {
|
293 | const data = {
|
294 | image: {
|
295 | bytes: Array.from(new Uint8Array(buffer))
|
296 | }
|
297 | };
|
298 | // 얼굴 인식 API 요청
|
299 | collector.fetchFaceDetect(data);
|
300 | });
|
301 | ```
|
302 |
|
303 | ### collector#fetchBehaviorDetect(bodyData: {file:FormData}, queryParams: { camLocation: string, reqTime: number })
|
304 |
|
305 | - 행동 감지 요청 API
|
306 |
|
307 | #### [URL Parameter]
|
308 |
|
309 | | 이름 | 타입 | 설명 | 필수 여부 |
|
310 | | ----------- | ------ | ----------------------------------------- | --------- |
|
311 | | camLocation | String | 카메라 위치 정보(side(측면), front(정면)) | O |
|
312 | | reqTime | long | 요청 시간(timestamp 10자리)(초 단위까지) | O |
|
313 |
|
314 | #### [Request Body]
|
315 |
|
316 | | 이름 | 타입 | 설명 | 필수 여부 |
|
317 | | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------ | --------- |
|
318 | | file | Binary | 이미지 파일<br>이미지 권장 사항<br>side (Size : 640 x 360, 확장자 : jpg, jpeg)<br>front (Size : 640 x 480, 확장자 : jpg, jpeg) | O |
|
319 |
|
320 | #### Example
|
321 |
|
322 | ```js
|
323 | // request body
|
324 | const formData = new FormData();
|
325 | formData.append('file', new File([imageBlob], 'cam-snapshot.jpg', { type: 'image/jpeg' }));
|
326 | // request params
|
327 | const query = {
|
328 | reqTime: Math.floor(Date.now() / 1000),
|
329 | camLocation: 'front'
|
330 | };
|
331 | // 행동 감지 요청 API
|
332 | collector.fetchBehaviorDetect(formData, query);
|
333 | ```
|
334 |
|
335 | ### collector#fetchVoiceDetect(data: {file: BinaryData}, queryParams: { reqTime: number })
|
336 |
|
337 | - 음성 감지 요청 API
|
338 |
|
339 | #### [URL Parameter]
|
340 |
|
341 | | 이름 | 타입 | 설명 | 필수 여부 |
|
342 | | ------- | ---- | ---------------------------------------- | --------- |
|
343 | | reqTime | long | 요청 시간(timestamp 10자리)(초 단위까지) | O |
|
344 |
|
345 | #### [Request Body]
|
346 |
|
347 | | 이름 | 타입 | 설명 | 필수 여부 |
|
348 | | ---- | ------ | ----------------------------------------------------------------------------------------------- | --------- |
|
349 | | file | Binary | 음성 파일<br>(지원 형식 .wav, .wave, .webm)<br>(권장 16bit, 16,000 sampling rate, mono channel) | O |
|
350 |
|
351 | #### Example
|
352 |
|
353 | ```js
|
354 | // request body
|
355 | const formData = new FormData();
|
356 | formData.append('file', new File([imageBlob], 'cam-snapshot.jpg', { type: 'image/jpeg' }));
|
357 | // request params
|
358 | const query = { reqTime: Math.floor(Date.now() / 1000) };
|
359 | // 음성 감지 요청 API
|
360 | collector.fetchVoiceDetect(formData, query);
|
361 | ```
|
362 |
|
363 | ### collector#registerBehaviorReg(bodyData: {file:FormData})
|
364 |
|
365 | - 가운데 시선 정보 등록 API
|
366 |
|
367 | ### [Request Body]
|
368 |
|
369 | | 이름 | 타입 | 설명 | 필수 여부 |
|
370 | | ---- | ------ | --------------------------------------------------------------- | --------- |
|
371 | | file | Binary | 이미지 파일<br>권장 사항 (Size : 640 x 480, 확장자 : jpg, jpeg) | O |
|
372 |
|
373 | #### Example
|
374 |
|
375 | ```js
|
376 | // request body
|
377 | const formData = new FormData();
|
378 | formData.append('file', new File([imageBlob], 'cam-snapshot.jpg', { type: 'image/jpeg' }));
|
379 | // 가운데 시선 정보 등록 API
|
380 | collector.registerBehaviorReg(formData);
|
381 | ```
|
382 |
|
383 | ### collector#preCheckSideCamera(bodyData: {file:FormData})
|
384 |
|
385 | - 측면 카메라 사전 검증 API
|
386 |
|
387 | ### [Request Body]
|
388 |
|
389 | | 이름 | 타입 | 설명 | 필수 여부 |
|
390 | | ---- | ------ | --------------------------------------------------------------- | --------- |
|
391 | | file | Binary | 이미지 파일<br>권장 사항 (Size : 640 x 360, 확장자 : jpg, jpeg) | O |
|
392 |
|
393 | #### Example
|
394 |
|
395 | ```js
|
396 | // request body
|
397 | const formData = new FormData();
|
398 | formData.append('file', new File([imageBlob], 'cam-snapshot.jpg', { type: 'image/jpeg' }));
|
399 | // 측면 카메라 사전 검증 API
|
400 | collector.preCheckSideCamera(formData);
|
401 | ```
|