Link/linker.js

import { addEvent, getElement, isOneOf, removeEvent } from '../util'
import { emptyCleanups, KakaoError, processRules, UA } from '../common'

import { makeCustomLink, makeDefaultLink, makeScrapLink } from './linkGenerator'
import { capitalize } from './common'
import webSender from './webSender'
import talkSender from './talkSender'
import rules from './rules'

/**
 * 메시지에서 콘텐츠 영역이나 버튼 클릭 시에 이동되는 링크 정보 오브젝트입니다. 오브젝트 내 프로퍼티 중 하나 이상은 반드시 존재해야 합니다.
 * @alias LinkObject
 * @typedef {Object} LinkObject
 * @property {String} [webUrl] PC버전 카카오톡에서 사용하는 웹 링크 URL
 * @property {String} [mobileWebUrl] 모바일 카카오톡에서 사용하는 웹 링크 URL
 * @property {String} [androidExecParams] 안드로이드 카카오톡에서 사용하는 앱 링크 URL에 사용될 파라미터
 * @property {String} [iosExecParams] iOS 카카오톡에서 사용하는 앱 링크 URL에 사용될 파라미터
 * @memberof Kakao.Link
 */
/**
 * 콘텐츠의 내용을 담고 있는 오브젝트입니다.
 * @alias ContentObject
 * @typedef {Object} ContentObject
 * @property {String} title 콘텐츠의 타이틀
 * @property {String} imageUrl 콘텐츠의 이미지 URL
 * @property {LinkObject} link 	콘텐츠 클릭 시 이동할 링크 정보
 * @property {Number} [imageWidth] 콘텐츠의 이미지 너비 (단위: 픽셀)
 * @property {Number} [imageHeight] 콘텐츠의 이미지 높이 (단위: 픽셀)
 * @property {String} [description] 콘텐츠의 상세 설명
 * @memberof Kakao.Link
 */
/**
 * 좋아요 수, 댓글 수 등의 소셜 정보를 표현하기 위해 사용되는 오브젝트입니다.
 * @alias SocialObject
 * @typedef {Object} SocialObject
 * @property {Number} [likeCount] 콘텐츠의 좋아요 수
 * @property {Number} [commentCount] 콘텐츠의 댓글 수
 * @property {Number} [sharedCount] 콘텐츠의 공유 수
 * @property {Number} [viewCount] 콘텐츠의 조회 수
 * @property {Number} [subscriberCount] 콘텐츠의 구독 수
 * @memberof Kakao.Link
 */
/**
 * 메시지 하단에 추가되는 버튼 오브젝트입니다.
 * @alias ButtonObject
 * @typedef {Object} ButtonObject
 * @property {String} title 버튼의 타이틀
 * @property {LinkObject} link 버튼 클릭 시 이동할 링크 정보
 * @memberof Kakao.Link
 */
/**
 * 가격 정보를 표현하기 위해 사용되는 오브젝트입니다.
 * @alias CommerceObject
 * @typedef {Object} CommerceObject
 * @property {Number} regularPrice 정상가격
 * @property {Number} [discountPrice] 할인된 가격
 * @property {Number} [discountRate] 할인율
 * @property {Number} [fixedDiscountPrice] 정액 할인 가격, 할인율과 동시 사용불가
 * @memberof Kakao.Link
 */

/**
 * @alias DefaultFeedSettings
 * @typedef {Object} DefaultFeedSettings
 * @property {String} objectType 고정값 "feed"
 * @property {ContentObject} content 메인 콘텐츠
 * @property {SocialObject} [social] 소셜 정보
 * @property {String} [buttonTitle] 버튼명, [앱 설정]에 따른 기본 링크 사용, buttonTitle과 buttons 함께 있을 경우 buttons가 적용됨
 * @property {ButtonObject[]} [buttons] 버튼, 링크 설정 가능, buttonTitle과 buttons 함께 있을 경우 buttons가 적용됨
 * @property {Boolean} [installTalk=false] 카카오톡이 설치되어 있지 않은 경우 마켓의 카카오톡 설치 페이지로 이동합니다.
 * @property {Function} [callback] 카카오링크 웹공유에서 공유 버튼 클릭 시 호출되는 콜백 함수 (IE 제외)
 * @property {Object|String} [serverCallbackArgs] 카카오링크 공유 시 전송되는 링크 콜백에 포함되는 파라미터. [링크 콜백 가이드로 이동](https://developers.kakao.com/docs/latest/ko/message/js#set-kakaolink-callback)
 * @memberof Kakao.Link
 */
/**
 * @alias DefaultListSettings
 * @typedef {Object} DefaultListSettings
 * @property {String} objectType 고정값 "list"
 * @property {String} headerTitle 헤더 타이틀
 * @property {LinkObject} headerLink 헤더 링크
 * @property {ContentObject[]} contents 메인 콘텐츠
 * @property {String} [buttonTitle] 버튼명, [앱 설정]에 따른 기본 링크 사용, buttonTitle과 buttons 함께 있을 경우 buttons가 적용됨
 * @property {ButtonObject[]} [buttons] 버튼, 링크 설정 가능, buttonTitle과 buttons 함께 있을 경우 buttons가 적용됨
 * @property {Boolean} [installTalk=false] 카카오톡이 설치되어 있지 않은 경우 마켓의 카카오톡 설치 페이지로 이동합니다.
 * @property {Function} [callback] 카카오링크 웹공유에서 공유 버튼 클릭 시 호출되는 콜백 함수 (IE 제외)
 * @property {Object|String} [serverCallbackArgs] 카카오링크 공유 시 전송되는 링크 콜백에 포함되는 파라미터. [링크 콜백 가이드로 이동](https://developers.kakao.com/docs/latest/ko/message/js#set-kakaolink-callback)
 * @memberof Kakao.Link
 */
/**
 * @alias DefaultLocationSettings
 * @typedef {Object} DefaultLocationSettings
 * @property {String} objectType 고정값 "location"
 * @property {ContentObject} content 메인 콘텐츠
 * @property {String} address 지도 뷰에서 사용 할 주소, ex.성남시 분당구 판교역로 235
 * @property {String} [addressTitle] 지도 뷰에서 사용될 주소명, ex.카카오 본사
 * @property {SocialObject} [social] 소셜 정보
 * @property {String} [buttonTitle] 버튼명, [앱 설정]에 따른 기본 링크 사용, buttonTitle과 buttons 함께 있을 경우 buttons가 적용됨
 * @property {ButtonObject[]} [buttons] 버튼, 링크 설정 가능, buttonTitle과 buttons 함께 있을 경우 buttons가 적용됨
 * @property {Boolean} [installTalk=false] 카카오톡이 설치되어 있지 않은 경우 마켓의 카카오톡 설치 페이지로 이동합니다.
 * @property {Function} [callback] 카카오링크 웹공유에서 공유 버튼 클릭 시 호출되는 콜백 함수 (IE 제외)
 * @property {Object|String} [serverCallbackArgs] 카카오링크 공유 시 전송되는 링크 콜백에 포함되는 파라미터. [링크 콜백 가이드로 이동](https://developers.kakao.com/docs/latest/ko/message/js#set-kakaolink-callback)
 * @memberof Kakao.Link
 */
/**
 * @alias DefaultCommerceSettings
 * @typedef {Object} DefaultCommerceSettings
 * @property {String} objectType 고정값 "commerce"
 * @property {ContentObject} content 메인 콘텐츠
 * @property {CommerceObject} commerce 가격 정보
 * @property {String} [buttonTitle] 버튼명, [앱 설정]에 따른 기본 링크 사용, buttonTitle과 buttons 함께 있을 경우 buttons가 적용됨
 * @property {ButtonObject[]} [buttons] 버튼, 링크 설정 가능, buttonTitle과 buttons 함께 있을 경우 buttons가 적용됨
 * @property {Boolean} [installTalk=false] 카카오톡이 설치되어 있지 않은 경우 마켓의 카카오톡 설치 페이지로 이동합니다.
 * @property {Function} [callback] 카카오링크 웹공유에서 공유 버튼 클릭 시 호출되는 콜백 함수 (IE 제외)
 * @property {Object|String} [serverCallbackArgs] 카카오링크 공유 시 전송되는 링크 콜백에 포함되는 파라미터. [링크 콜백 가이드로 이동](https://developers.kakao.com/docs/latest/ko/message/js#set-kakaolink-callback)
 * @memberof Kakao.Link
 */
/**
 * @alias DefaultTextSettings
 * @typedef {Object} DefaultTextSettings
 * @property {String} objectType 고정값 "text"
 * @property {String} text 최대 200자의 텍스트
 * @property {String} [buttonTitle] 버튼명, [앱 설정]에 따른 기본 링크 사용, buttonTitle과 buttons 함께 있을 경우 buttons가 적용됨
 * @property {ButtonObject[]} [buttons] 버튼, 링크 설정 가능, buttonTitle과 buttons 함께 있을 경우 buttons가 적용됨
 * @property {Boolean} [installTalk=false] 카카오톡이 설치되어 있지 않은 경우 마켓의 카카오톡 설치 페이지로 이동합니다.
 * @property {Function} [callback] 카카오링크 웹공유에서 공유 버튼 클릭 시 호출되는 콜백 함수 (IE 제외)
 * @property {Object|String} [serverCallbackArgs] 카카오링크 공유 시 전송되는 링크 콜백에 포함되는 파라미터. [링크 콜백 가이드로 이동](https://developers.kakao.com/docs/latest/ko/message/js#set-kakaolink-callback)
 * @memberof Kakao.Link
 */

/**
 * 링크 타입 (Feed, List, Location, Commerce, Text)에 맞는 Object를 구성하여 링크를 쉽게 전송할 수 있습니다.
 * @function createDefaultButton
 * @param {DefaultFeedSettings|DefaultListSettings|DefaultLocationSettings|DefaultCommerceSettings|DefaultTextSettings} settings 카카오링크와 관련된 설정을 key/value로 전달합니다.
 * @param {String|HTMLElement} settings.container DOM Element 또는 Element의 ID Selector를 넘기면, 해당 Element를 클릭할 때 카카오링크가 전송됩니다.
 * @see {@link Kakao.Link.sendDefault} 직접 카카오링크 버튼을 제작하여 사용할 때 이용하세요.
 * @see {@link https://developers.kakao.com/tool/demo/message/kakaolink?default_template=feed|데모 보러가기}
 * @memberof Kakao.Link
 */
export function createDefaultButton(settings) {
  if (!settings.objectType || !isOneOf(rules.defaultObjectTypes)(settings.objectType)) {
    throw new KakaoError(`objectType should be one of (${rules.defaultObjectTypes.join(', ')})`)
  }

  const rule = rules[`create${capitalize(settings.objectType)}Button`]
  settings = processRules(settings, rule, 'Link.createDefaultButton')

  addClickEvent(settings, 'default')
}

/**
 * 링크 타입 (Feed, List, Location, Commerce, Text)에 맞는 Object를 구성하여 링크를 쉽게 전송할 수 있습니다.
 * @function sendDefault
 * @param {DefaultFeedSettings|DefaultListSettings|DefaultLocationSettings|DefaultCommerceSettings|DefaultTextSettings} settings 카카오링크와 관련된 설정을 key/value로 전달합니다.
 * @see {@link Kakao.Link.createDefaultButton} 직접 카카오링크 공유 버튼을 제작하여 사용할 필요가 없는 경우 유용합니다.
 * @see {@link https://developers.kakao.com/tool/demo/message/kakaolink?method=send&default_template=feed|데모 보러가기}
 * @memberof Kakao.Link
 */
export function sendDefault(settings) {
  if (!settings.objectType || !isOneOf(rules.defaultObjectTypes)(settings.objectType)) {
    throw new KakaoError(`objectType should be one of (${rules.defaultObjectTypes.join(', ')})`)
  }

  const rule = rules[`send${capitalize(settings.objectType)}`]
  settings = processRules(settings, rule, 'Link.sendDefault')

  doSend(settings, 'default')
}

/**
 * 사이트의 메타 정보를 활용하여 링크를 전송합니다.
 * @function createScrapButton
 * @param {Object} settings 카카오링크와 관련된 설정을 key/value로 전달합니다.
 * @param {String|HTMLElement} settings.container DOM Element 또는 Element의 ID Selector를 넘기면, 해당 Element를 클릭할 때 카카오링크가 전송됩니다.
 * @param {String} settings.requestUrl 스크랩 할 사이트 URL, 해당 사이트의 메타 정보를 토대로 링크를 생성
 * @param {Number} [settings.templateId] 메시지 아이디, [앱 설정] - [메시지 템플릿 v2] 확인
 * @param {Object} [settings.templateArgs] 메시지에 전달할 Argument, ex) {'name':'kakao', 'url':'https://developers.kakao.com'}
 * @param {Boolean} [settings.installTalk=false] 카카오톡이 설치되어 있지 않은 경우 마켓의 카카오톡 설치 페이지로 이동합니다.
 * @param {Function} [settings.callback] 카카오링크 웹공유에서 공유 버튼 클릭 시 호출되는 콜백 함수 (IE 제외)
 * @param {Object|String} [settings.serverCallbackArgs] 카카오링크 공유 시 전송되는 링크 콜백에 포함되는 파라미터. [링크 콜백 가이드로 이동](https://developers.kakao.com/docs/latest/ko/message/js#set-kakaolink-callback)
 * @see {@link Kakao.Link.sendScrap} 직접 카카오링크 버튼을 제작하여 사용할 때 이용하세요.
 * @see {@link https://developers.kakao.com/tool/demo/message/kakaolink?message_type=scrap|데모 보러가기}
 * @memberof Kakao.Link
 */
export function createScrapButton(settings) {
  settings = processRules(settings, rules.createScrapButton, 'Link.createScrapButton')
  addClickEvent(settings, 'scrap')
}

/**
 * 사이트의 메타 정보를 활용하여 링크를 전송합니다.
 * @function sendScrap
 * @param {Object} settings 카카오링크와 관련된 설정을 key/value로 전달합니다.
 * @param {String} settings.requestUrl 스크랩 할 사이트 URL, 해당 사이트의 메타 정보를 토대로 링크를 생성
 * @param {Number} [settings.templateId] 메시지 아이디, [앱 설정] - [메시지 템플릿 v2] 확인
 * @param {Object} [settings.templateArgs] 메시지에 전달할 Argument, ex) {'name':'kakao', 'url':'https://developers.kakao.com'}
 * @param {Boolean} [settings.installTalk=false] 카카오톡이 설치되어 있지 않은 경우 마켓의 카카오톡 설치 페이지로 이동합니다.
 * @param {Function} [settings.callback] 카카오링크 웹공유에서 공유 버튼 클릭 시 호출되는 콜백 함수 (IE 제외)
 * @param {Object|String} [settings.serverCallbackArgs] 카카오링크 공유 시 전송되는 링크 콜백에 포함되는 파라미터. [링크 콜백 가이드로 이동](https://developers.kakao.com/docs/latest/ko/message/js#set-kakaolink-callback)
 * @see {@link Kakao.Link.createScrapButton} 직접 카카오링크 공유 버튼을 제작하여 사용할 필요가 없는 경우 유용합니다.
 * @see {@link https://developers.kakao.com/tool/demo/message/kakaolink?method=send&message_type=scrap|데모 보러가기}
 * @memberof Kakao.Link
 */
export function sendScrap(settings) {
  settings = processRules(settings, rules.sendScrap, 'Link.sendScrap')
  doSend(settings, 'scrap')
}

/**
 * 원하는 형태의 메시지 템플릿을 만든 후 전송합니다. [메시지 템플릿 가이드로 이동](https://developers.kakao.com/docs/latest/ko/message/message-template)
 * @function createCustomButton
 * @param {Object} settings 카카오링크와 관련된 설정을 key/value로 전달합니다.
 * @param {String|HTMLElement} settings.container DOM Element 또는 Element의 ID Selector를 넘기면, 해당 Element를 클릭할 때 카카오링크가 전송됩니다.
 * @param {Number} settings.templateId 메시지 아이디, [앱 설정] - [메시지 템플릿 v2] 확인
 * @param {Object} [settings.templateArgs] 메시지에 전달할 Argument, ex) {'name':'kakao', 'url':'https://developers.kakao.com'}
 * @param {Boolean} [settings.installTalk=false] 카카오톡이 설치되어 있지 않은 경우 마켓의 카카오톡 설치 페이지로 이동합니다.
 * @param {Function} [settings.callback] 카카오링크 웹공유에서 공유 버튼 클릭 시 호출되는 콜백 함수 (IE 제외)
 * @param {Object|String} [settings.serverCallbackArgs] 카카오링크 공유 시 전송되는 링크 콜백에 포함되는 파라미터. [링크 콜백 가이드로 이동](https://developers.kakao.com/docs/latest/ko/message/js#set-kakaolink-callback)
 * @see {@link Kakao.Link.sendCustom} 직접 카카오링크 버튼을 제작하여 사용할 때 이용하세요.
 * @see {@link https://developers.kakao.com/tool/demo/message/kakaolink?message_type=custom|데모 보러가기}
 * @memberof Kakao.Link
 */
export function createCustomButton(settings) {
  settings = processRules(settings, rules.createCustomButton, 'Link.createCustomButton')
  addClickEvent(settings, 'custom')
}

/**
 * 원하는 형태의 메시지 템플릿을 만든 후 전송합니다. [메시지 템플릿 가이드로 이동](https://developers.kakao.com/docs/latest/ko/message/message-template)
 * @function sendCustom
 * @param {Object} settings 카카오링크와 관련된 설정을 key/value로 전달합니다.
 * @param {Number} settings.templateId 메시지 아이디, [앱 설정] - [메시지 템플릿 v2] 확인
 * @param {Object} [settings.templateArgs] 메시지에 전달할 Argument, ex) {'name':'kakao', 'url':'https://developers.kakao.com'}
 * @param {Boolean} [settings.installTalk=false] 카카오톡이 설치되어 있지 않은 경우 마켓의 카카오톡 설치 페이지로 이동합니다.
 * @param {Function} [settings.callback] 카카오링크 웹공유에서 공유 버튼 클릭 시 호출되는 콜백 함수 (IE 제외)
 * @param {Object|String} [settings.serverCallbackArgs] 카카오링크 공유 시 전송되는 링크 콜백에 포함되는 파라미터. [링크 콜백 가이드로 이동](https://developers.kakao.com/docs/latest/ko/message/js#set-kakaolink-callback)
 * @see {@link Kakao.Link.createCustomButton} 직접 카카오링크 공유 버튼을 제작하여 사용할 필요가 없는 경우 유용합니다.
 * @see {@link https://developers.kakao.com/tool/demo/message/kakaolink?method=send&message_type=custom|데모 보러가기}
 * @memberof Kakao.Link
 */
export function sendCustom(settings) {
  settings = processRules(settings, rules.sendCustom, 'Link.sendCustom')
  doSend(settings, 'custom')
}

function addClickEvent(settings, linkType) {
  const container$ = getElement(settings.container)
  if (!container$) {
    throw new KakaoError('container is required for KakaoTalk Link: pass in element or id')
  }

  const clickHandler = e => {
    e.preventDefault()
    e.stopPropagation()

    doSend(settings, linkType)
  }

  addEvent(container$, 'click', clickHandler)
  cleanups.push(() => {
    removeEvent(container$, 'click', clickHandler)
  })
}

const linkTypeMapper = {
  default: {
    makeLinkFunc: makeDefaultLink,
    requestUrl: '/v2/api/kakaolink/talk/template/default',
  },
  scrap: {
    makeLinkFunc: makeScrapLink,
    requestUrl: '/v2/api/kakaolink/talk/template/scrap',
  },
  custom: {
    makeLinkFunc: makeCustomLink,
    requestUrl: '/v2/api/kakaolink/talk/template/validate',
  },
}

function doSend(settings, linkType) {
  const { makeLinkFunc, requestUrl } = linkTypeMapper[linkType]
  const linkObj = makeLinkFunc(settings)

  const isIpad = (UA.os.ios && UA.platform === 'tablet')
  if (!settings.throughTalk || (UA.platform !== 'mobile' && !isIpad)) {
    webSender.send(settings, linkType, linkObj)
  } else {
    talkSender.send(settings, requestUrl, linkObj)
  }
}

const cleanups = []
export function cleanup() {
  emptyCleanups(cleanups)
}