Story/request.js

import web2app from '../lib/web2app'
import { addEvent, buildQueryString, extend, getElement, map, removeEvent } from '../util'
import { applyAttributes, emptyCleanups, getAppKey, getInstallUrl, KakaoError, KAKAO_AGENT, logDebug, processRules, URL, VERSION, windowOpen } from '../common'

import rules from './rules'

/**
 * 지정한 Element를 클릭할 때 카카오스토리 공유 창이 열리도록 합니다.
 * @function createShareButton
 * @param {Object} settings 카카오스토리 공유 버튼과 관련된 설정을 key/value로 전달합니다.
 * @param {String|HTMLElement} settings.container DOM Element 또는 Element의 ID Selector를 넘기면, 해당 Element를 클릭할 때 카카오스토리 공유 창이 열립니다.
 * @param {String} [settings.url=${current page's URL}] 카카오스토리로 공유할 웹 페이지의 URL.
 * @param {String} [settings.text=""] 공유 창에 표시할 텍스트
 * @see {@link https://developers.kakao.com/tool/demo/story/share?method=web-button|데모 보러가기}
 * @memberof Kakao.Story
 */
export function createShareButton(settings) {
  const container$ = getElement(settings.container)
  if (!container$) {
    throw new KakaoError('container is required for Story.createShareButton: pass in element or id')
  } else {
    applyAttributes(settings, container$, {
      url: 'data-url',
    })
  }

  settings = processRules(settings, rules.createShareButton, 'Story.createShareButton')

  const anchor$ = createAnchorImage(URL.storyIcon, '카카오스토리 웹 공유 버튼')
  container$.appendChild(anchor$)

  const clickHandler = e => {
    e.preventDefault()
    openSharePopup(settings)
  }

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

function createAnchorImage(imgSrc, imgTitle) {
  const a$ = document.createElement('a')
  a$.setAttribute('href', '#')

  const img$ = document.createElement('img')
  img$.setAttribute('src', imgSrc)
  img$.setAttribute('title', imgTitle)
  img$.setAttribute('alt', imgTitle)

  a$.appendChild(img$)
  return a$
}

/**
 * 카카오스토리 웹 공유 창을 엽니다.
 * @function share
 * @param {Object} settings 카카오스토리 공유 버튼과 관련된 설정을 key/value로 전달합니다.
 * @param {String} [settings.url=${current page's URL}] 카카오스토리로 공유할 웹페이지의 URL
 * @param {String} [settings.text=""] 공유 창에 표시할 텍스트
 * @see {@link https://developers.kakao.com/tool/demo/story/share|데모 보러가기}
 * @memberof Kakao.Story
 */
export function share(settings) {
  settings = processRules(settings, rules.share, 'Story.share')
  openSharePopup(settings)
}

function openSharePopup(settings) {
  const params = extend({
    url: settings.url,
  }, makeStoryParams())

  if (settings.text) {
    params.text = settings.text
  }

  windowOpen(
    `${URL.storyShare}?${buildQueryString(params)}`,
    'kakaostory_social_plugin',
    'width=670, height=800, scrollbars=yes',
  )
}

/**
 * 모바일 환경에서 카카오스토리 앱 공유 화면을 엽니다.
 * @function open
 * @param {Object} settings 카카오스토리 공유 버튼과 관련된 설정을 key/value로 전달합니다.
 * @param {Boolean} [settings.install=false] 카카오스토리 앱이 설치되어 있지 않은 경우 마켓의 카카오스토리 설치 페이지로 이동합니다.
 * @param {String} [settings.url=${current page's URL}] 카카오스토리로 공유할 웹 페이지의 URL
 * @param {String} [settings.text=""] 공유 창에 표시할 텍스트
 * @param {Obejct} [settings.urlInfo] 위에 입력한 url에 대한 추가적인 정보 (입력하지 않을 경우 스크랩 서버가 자동으로 생성)
 * @param {String} settings.urlInfo.title 스크랩 영역에 표시할 제목
 * @param {String} [settings.urlInfo.desc] 스크랩 영역에 표시할 설명
 * @param {String} [settings.urlInfo.name] 스크랩 영역에 표시할 사이트 이름
 * @param {String[]} [settings.urlInfo.images] 스크랩 영역에 표시할 대표 이미지 URL
 * @example
 * // 1. 필수 옵션만 사용. 메타 정보는 스크랩 서버가 자동으로 생성
 * Kakao.Story.open({
 *   url: 'http://my.share.url.com',
 *   text: 'Text to share'
 * })
 * @example
 * // 2. 스크랩된 url의 메타 정보를 바꾸고 싶을때
 * Kakao.Story.open({
 *   url: 'http://my.share.url.com',
 *   text: 'Text to share',
 *   urlInfo: {
 *     title: 'The title of a shared site',
 *     desc: 'A description of a website',
 *     name: 'The name at the bottom',
 *     images: ['http://my.image.url']
 *   }
 * })
 * @see {@link https://developers.kakao.com/tool/demo/story/share?method=app|데모 보러가기}
 * @memberof Kakao.Story
 */
export function open(settings) {
  settings = processRules(settings, rules.open, 'Story.open')

  const storyPostScheme = makeStoryPostScheme(settings)
  const androidIntent = [
    `intent:${storyPostScheme}#Intent`,
    `${(settings.install ? 'package=com.kakao.story;' : '')}end;`
  ].join(';')

  const web2appOptions = {
    urlScheme: storyPostScheme,
    intentURI: androidIntent,
    appName: 'KakaoStory',
    storeURL: getInstallUrl('com.kakao.story', '486244601'),
    onUnsupportedEnvironment() {
      settings.fail && settings.fail()
    },
  }

  try {
    web2app(web2appOptions)
  } catch (error) {
    logDebug(error)
  }
}

function makeStoryPostScheme(settings) {
  const domain = location.hostname || ''

  const params = extend({
    apiver: '1.0',
    appver: VERSION,
    appid: domain,
    appname: domain,
    post: settings.text ? `${settings.text}\n${settings.url}` : settings.url,
  }, makeStoryParams())

  if (settings.urlInfo) {
    params.urlinfo = JSON.stringify(settings.urlInfo)
    params.appname = settings.urlInfo.name || params.appname
  }

  return `${URL.storyPostScheme}?${buildQueryString(params)}`
}

/**
 * 지정한 Element를 클릭할 때 카카오스토리 채널로부터 소식을 받도록 합니다.
 * @function createFollowButton
 * @param {Object} settings 카카오스토리 소식받기 버튼과 관련된 설정을 key/value로 전달합니다.
 * @param {String|HTMLElement} settings.container DOM Element 또는 Element의 ID Selector를 넘기면, 해당 Element를 클릭할 때 지정한 채널을 구독합니다.
 * @param {String} settings.id 소식을 받을 카카오스토리 채널 ID. ex) kakao
 * @param {Boolean} [settings.showFollowerCount=true] 구독자 수를 노출합니다.
 * @param {String} [settings.type="horizontal"] 구독자 수를 노출할 형태를 정합니다.
 * @see {@link https://developers.kakao.com/tool/demo/story/follow|데모 보러가기}
 * @memberof Kakao.Story
 */
export function createFollowButton(settings) {
  const container$ = getElement(settings.container)
  if (!container$) {
    throw new KakaoError('container is required for Story.createFollowButton: pass in element or id')
  } else {
    applyAttributes(settings, container$, {
      id: 'data-id',
      showFollowerCount: 'data-show-follower-count',
      type: 'data-type',
    })
  }

  settings = processRules(settings, rules.createFollowButton, 'Story.createFollowButton')

  const { iframe$, messageHandler } = createStoryFollowIframe(settings)
  container$.appendChild(iframe$)

  addEvent(window, 'message', messageHandler)
  cleanups.push(() => {
    removeEvent(window, 'message', messageHandler)
    container$.removeChild(iframe$)
  })
}

let _storyFollowIframeId = 0
function createStoryFollowIframe(settings) {
  const iframeId = _storyFollowIframeId++
  let iframeWidth = settings.showFollowerCount && settings.type === 'horizontal' ? 85 : 59
  let iframeHeight = settings.showFollowerCount && settings.type === 'vertical' ? 46 : 20

  const iframe$ = document.createElement('iframe')
  iframe$.src = makeStoryFollowUrl(settings, iframeId)
  iframe$.setAttribute('frameborder', '0')
  iframe$.setAttribute('marginwidth', '0')
  iframe$.setAttribute('marginheight', '0')
  iframe$.setAttribute('scrolling', 'no')
  iframe$.setAttribute('style', `width:${iframeWidth}px; height:${iframeHeight}px;`)

  const messageHandler = e => {
    if (e.data && /\.kakao\.com$/.test(e.origin) && typeof e.data === 'string') {
      const [ originIframeId, afterWidth, afterHeight ] = map(e.data.split(','), e => parseInt(e, 10))

      if (originIframeId === iframeId) {
        if (iframeWidth !== afterWidth) {
          iframeWidth = afterWidth
          iframe$.style.width = `${iframeWidth}px`
        }
        if (iframeHeight !== afterHeight) {
          iframeHeight = afterHeight
          iframe$.style.height = `${iframeHeight}px`
        }
      }
    }
  }

  return { iframe$, messageHandler }
}

function makeStoryFollowUrl(settings, iframeId) {
  const params =  extend({
    id: settings.id,
    type: settings.type,
    hideFollower: !settings.showFollowerCount,
    frameId: iframeId,
  }, makeStoryParams())

  return `${URL.storyChannelFollow}?${buildQueryString(params)}`
}

function makeStoryParams() {
  const params = {
    kakao_agent: KAKAO_AGENT,
  }

  if (getAppKey() !== null) {
    params.app_key = getAppKey()
  }

  return params
}

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