import { Promise } from 'es6-promise'
import EasyXDM from '../lib/easyXDM'
import { each, isString, map } from '../util'
import { emptyCleanups, guardCreateEasyXDM, KakaoError, KAKAO_AGENT, logDebug, processRules, serializeFile, URL } from '../common'
import rules from './rules'
import { accessToken } from './authType'
/**
* 호스트가 `https://dapi.kakao.com`인 API (검색, 로컬, 비전, 번역)는 제외되며, Ajax를 통해 직접 요청할 수 있습니다.
* Admin 키를 사용하는 API (인증, 푸시, 페이)는 제외됩니다.
* 푸시 알림 기능은 지원되지 않습니다.
* @typedef {string} APIUrl
* @alias APIUrl
* @example /v2/user/me
* @see {@link https://developers.kakao.com/tool/demo/login/userme|데모 보러가기}
* @memberof Kakao.API
*/
/**
* 카카오 API를 호출할 수 있습니다.
* @function request
* @param {Object} settings API 호출과 관련된 설정을 key/value로 전달합니다.
* @param {APIUrl} settings.url 호출할 API URL
* @param {Object} [settings.data] API에 전달할 파라미터
* @param {FileList|File[]|Blob[]} [settings.files] 파일 첨부가 필요한 API에서 이용하는 파일 파라미터
* @param {Function} [settings.success] API 호출이 성공할 경우 결과를 받을 콜백 함수
* @param {Function} [settings.fail] API 호출이 실패할 경우 결과를 받을 콜백 함수
* @param {Function} [settings.always] API 호출이 성공하거나 실패할 경우 항상 호출할 콜백 함수
* @returns {Promise}
* @memberof Kakao.API
*/
let proxyForRequest = null
export function request(settings) {
settings = processRules(settings, rules.request, 'API.request')
const url = settings.url
const urlRule = rules.api[url].data
if (urlRule) {
settings.data = processRules(settings.data, urlRule, `API.request - ${url}`)
}
if (!proxyForRequest) {
proxyForRequest = getProxy()
cleanups.push(() => {
proxyForRequest.destroy()
proxyForRequest = null
})
}
return new Promise((resolve, reject) => {
getConfig(settings).then(
config => {
proxyForRequest.request(
config,
res => {
settings.success(res)
settings.always(res)
resolve(res)
},
xdmError => {
const error = parseXdmError(xdmError)
settings.fail(error)
settings.always(error)
reject(error)
},
)
},
error => {
reject(error)
},
)
})
}
function getProxy() {
return guardCreateEasyXDM(() => {
return new EasyXDM.Rpc(
{
remote: URL.apiRemote,
},
{
remote: {
request: {},
},
},
)
})
}
function parseXdmError(xdmError) {
try {
logDebug(xdmError)
return JSON.stringify(xdmError.message.responseText)
} catch(e) {
return {
code: -777,
msg: 'Unknown error',
}
}
}
function getConfig(settings) {
const { url } = settings
const urlSpec = rules.api[url]
const stringifiedData = {}
each(settings.data, (value, key) => {
stringifiedData[key] = isString(value) ? value : JSON.stringify(value)
})
const config = {
url,
method: urlSpec.method,
headers: {
KA: KAKAO_AGENT,
Authorization: (urlSpec.authType || accessToken)(),
'Cache-Control': 'no-cache',
Pragma: 'no-cache', // IE 11에서 일부 응답(/v2/user/me)을 캐시하는 것을 방지
},
data: stringifiedData,
}
return new Promise((resolve, reject) => {
if (isFileRequired(url) || settings.data.file) {
const files = settings.files || settings.data.file
if (!files) {
throw new KakaoError(`'files' parameter should be set for ${url}`)
}
getFileConfig(files).then(
fileConfig => {
const searchParams = []
for (let prop in stringifiedData) {
if (prop !== 'file') {
searchParams.push(`${prop}=${encodeURIComponent(stringifiedData[prop])}`)
}
}
if (searchParams.length > 0) {
config.url += `?${searchParams.join('&')}`
}
config.file = fileConfig
resolve(config)
},
error => {
reject(error)
},
)
} else {
resolve(config)
}
})
}
function isFileRequired(url) {
return url === '/v1/api/story/upload/multi' ||
url === '/v2/api/talk/message/image/upload'
}
function getFileConfig(files) {
const serializePromises = map(files, file => {
return serializeFile(file).then(serialized => {
return {
name: file.name,
type: file.type,
str: serialized,
}
})
})
return new Promise((resolve, reject) => {
Promise.all(serializePromises).then(
serializedFiles => {
resolve({
paramName: 'file',
data: serializedFiles,
})
},
error => {
reject(error)
},
)
})
}
const cleanups = []
export function cleanup() {
emptyCleanups(cleanups)
}