channel.io ๋ถ„์„ ํ•˜๊ธฐ!

2021๋…„ 09์›” 30์ผ

TOC


channel.io

๐Ÿค“ intro

์ตœ๊ทผ FrontEnd ๊ณต๋ถ€๋ฅผ ํ•˜๋ฉด์„œ channel.io, crisp ์™€ ๊ฐ™์€ ํ”Œ๋Ÿฌ๊ทธ์ธ ์„œ๋น„์Šค๋Š” ํŠน์ • ์‚ฌ์ดํŠธ, ์„œ๋น„์Šค ์œ„์— ์ถ”๊ฐ€ ๋˜์–ด์„œ ๋™์ž‘์„ ํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ์ด๋•Œ ์–ด๋–ค ํ˜•ํƒœ๋กœ ๊ตฌํ˜„์ด ๋˜์–ด์„œ ์ž‘๋™์ด ๋˜์—ˆ๋Š”์ง€ ๊ถ๊ธˆ์ฆ๊ณผ ํ˜ธ๊ธฐ์‹ฌ์ด ๋ฐœ์ƒ ํ•˜์—ฌ์„œ ๋ถ„์„์„ ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

๐Ÿ”ฌ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ํ†ตํ•ด ํ™•์ธ ํ•ด๋ณด๊ธฐ!

์ผ๋‹จ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ด๋Ÿฐ ํ˜•ํƒœ์˜ ์„œ๋น„์Šค๋Š” iframe ๋ฅผ ํ†ตํ•ด ๊ตฌํ˜„์ด ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์œผ๋กœ ์•Œ๊ณ  ์žˆ๋Š”๋ฐ ํ•ด๋‹น ๋ถ€๋ถ„์ด ๋งž๋Š”์ง€ ์ง์ ‘ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ์ด์šฉํ•ด์„œ ํ™•์ธ์„ ํ•ด๋ด…๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ์ด์šฉํ•œ iframe ํ™•์ธ

Chrome ๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ์ผœ์„œ ์ง์ ‘ ํ•ด๋‹น element ๋ฅผ ํ™•์ธ์„ ํ•ด๋ณด๋‹ˆ ์œ„์™€ ๊ฐ™์ด iframe ์œผ๋กœ ๊ตฌ์„ฑ ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ผ๋‹จ iframe ๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ ์„œ๋น„์Šค ๋‚ด๋ถ€์—์„œ ๋™์ž‘์„ ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ์•Œ์•˜์ง€๋งŒ ์ด์ œ ์–ด๋–ค ํ˜•ํƒœ๋กœ ์‚ฌ์ดํŠธ ๋‚ด๋ถ€์—์„œ ์ƒํ˜ธ ์ž‘์šฉํ•˜๋ฉด์„œ ํ†ต์‹ ์„ ํ•˜๋Š” ๊ฑฐ์ง€์— ๋Œ€ํ•œ ์˜๋ฌธ์„ ๊ฐ€์งˆ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š”๋ฐ ์ด๋•Œ channel.io ์˜ ์ ์šฉ script๋ฅผ ๋ถ„์„ํ•ด๋ณด๋ฉด์„œ ์˜๋ฌธ์ ์„ ํ•ด๊ฒฐ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๐Ÿง Channel Plugin Scripts ๋ถ„์„ ํ•˜๊ธฐ!

์ผ๋‹จ channel.io ์˜ ๊ฐœ๋ฐœ์ž ๊ฐ€์ด๋“œ ๋ฌธ์„œ๋กœ ๊ฐ€์„œ Installation ํŽ˜์ด์ง€์—์„œ ์•ˆ๋‚ดํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋ถ„์„ ํ•ฉ๋‹ˆ๋‹ค!

[docs] web-installation#single-page-application

// ChannelService.js

class ChannelService {
  // ํ•ด๋‹น Class ์ƒ์„ฑ์ž ํ˜ธ์ถœ์‹œ `loadScript` ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ ํ•ฉ๋‹ˆ๋‹ค.
  constructor() {
    this.loadScript()
  }

  loadScript() {
    var w = window
    // ์ด๋ฏธ window ์ „์—ญ ๊ฐ์ฒด์— `ChannelIO` ๊ฐ€ ์กด์žฌ ํ•˜๋Š” ์ง€๋ฅผ ์ฒดํฌ ํ•˜์—ฌ ์ค‘๋ณต์œผ๋กœ Load ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ ํ•ฉ๋‹ˆ๋‹ค.
    if (w.ChannelIO) {
      return (window.console.error || window.console.log || function () {})(
        "ChannelIO script included twice."
      )
    }
    // ch ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  ๋‚ด๋ถ€์˜ cํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌ ๋ฐ›์€ `arguments` ๋ฅผ ์ „๋‹ฌ ํ•˜์—ฌ ์‹คํ–‰ ํ•ฉ๋‹ˆ๋‹ค.
    var ch = function () {
      ch.c(arguments)
    }
    // ch ๊ฐ์ฒด ๋‚ด๋ถ€์— q array๋ฅผ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.
    ch.q = []
    // ์ „๋‹ฌ ๋ฐ›์€ args๋ฅผ q์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    ch.c = function (args) {
      ch.q.push(args)
    }
    // window.ChannelIO ๋ฅผ ch๋กœ ์„ค์ • ํ•ฉ๋‹ˆ๋‹ค.
    w.ChannelIO = ch
    // boot script ๋ฅผ ์ •์˜ ํ•ฉ๋‹ˆ๋‹ค.
    function l() {
      // ๋งŒ์•ฝ channelIo๊ฐ€ ์ดˆ๊ธฐํ™” ๋˜์—ˆ๋‹ค๋ฉด ์•„๋ฌด ์ž‘์—…์„ ํ•˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
      if (w.ChannelIOInitialized) {
        return
      }
      // ์ดˆ๊ธฐํ™” ์ž‘์—… ์™„๋ฃŒ flag๋ฅผ ์„ธ์›Œ์ค๋‹ˆ๋‹ค.
      w.ChannelIOInitialized = true
      // script element๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
      var s = document.createElement("script")
      // javascript ๊ตฌ๋ฌธ์œผ๋กœ ํ•ด์„ ๋  ์ˆ˜ ์žˆ๋„๋ก ์ •์˜ ํ•ฉ๋‹ˆ๋‹ค.
      s.type = "text/javascript"
      // ๋น„๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
      s.async = true
      // channel.io๊ฐ€ ํ•ต์‹ฌ์ ์œผ๋กœ ๋™์ž‘ํ•˜๊ฒŒ๋  bunddle๋œ script src๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
      s.src = "https://cdn.channel.io/plugin/ch-plugin-web.js"
      // UTF-8๋กœ ํ•ต์„ ๋  ์ˆ˜ ์žˆ๋„๋ก ์•Œ๋ ค์ค๋‹ˆ๋‹ค.
      s.charset = "UTF-8"
      // document ๋‚ด๋ถ€์— ์žˆ๋Š” ์ฒซ๋ฒˆ์งธ script element๋ฅผ ๊ฐ€์ ธ ์˜ต๋‹ˆ๋‹ค.
      var x = document.getElementsByTagName("script")[0]
      // ์œ„์—์„œ ์ •์˜ํ•œ channel.io ์Šคํฌ๋ฆฝํŠธ ํƒœ๊ทธ๋ฅผ x ์ด์ „์— ์‚ฝ์ž… ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
      x.parentNode.insertBefore(s, x)
    }

    // Document์˜ ์ƒํƒœ๊ฐ€ complete ๋˜๋Š”, ๋กœ๋“œ๊ฐ€ ๋˜์–ด ์žˆ๋‹ค๋ฉด l๋ฅผ ํ˜ธ์ถœ ํ•œ๋‹ค.
    if (document.readyState === "complete") {
      l()
    } else if (window.attachEvent) {
      window.attachEvent("onload", l)
    } else {
      window.addEventListener("DOMContentLoaded", l, false)
      window.addEventListener("load", l, false)
    }
  }

  // ChannelIO์˜ boot ์ž‘์—…์„ ์ธ์ž๋กœ ๋„˜์–ด์˜จ settings ์˜ค๋ธŒ์ ํŠธ๋ฅผ ํ†ตํ•ด ์‹คํ–‰ํ•œ๋‹ค.
  boot(settings) {
    window.ChannelIO("boot", settings)
  }

  // ChannelIO ์ข…๋ฃŒ
  shutdown() {
    window.ChannelIO("shutdown")
  }
}

export default new ChannelService()

ํ•ด๋‹น ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ดํŽด ๋ณด๋ฉด CDN ์—์„œ script ๋ฅผ ๋ถˆ๋Ÿฌ์™€ ์‹คํ–‰ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ ChannelService ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด pluginKey ์™€ ์˜ต์…˜์„ ํ•จ๊ป˜ ์ „๋‹ฌํ•˜์—ฌ channel.io ๋ฅผ ์‹คํ–‰ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// Boot Channel as an anonymous user
ChannelService.boot({
  pluginKey: "YOUR_PLUGIN_KEY", //please fill with your plugin key
})

// Boot Channel as a registered user
ChannelService.boot({
  pluginKey: "YOUR_PLUGIN_KEY", //please fill with your plugin key
  profile: {
    name: "YOUR_USER_NAME", //fill with user name
    mobileNumber: "YOUR_USER_MOBILE_NUMBER", //fill with user phone number
    CUSTOM_VALUE_1: "VALUE_1", //any other custom meta data
    CUSTOM_VALUE_2: "VALUE_2",
  },
})

// Shutdown Channel
ChannelService.shutdown()

์ด์ œ ํ•œ๋ฒˆ CDN ์—์„œ ๋ถˆ๋Ÿฌ์˜จ script ๋ฅผ ๋ถ„์„ ํ•ด๋ด…๋‹ˆ๋‹ค!

// https://cdn.channel.io/plugin/ch-plugin-web.js

!(function () {
  var e
  if (
    // ์ง€์› ํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ €์™€ ๋ฒ„์ „์— ์†ํ•ด ์žˆ๋Š”์ง€ ๊ฒ€์ฆ
    ((e = window.navigator.userAgent),
    !/(Opera\/.+Opera Mobi.+Version\/((10|11)\.0|11\.1|11\.5|12\.(0|1)))|(Opera\/((10|11)\.0|11\.1|11\.5|12\.(0|1)).+Opera Mobi)|(Opera Mobi.+Opera(?:\/|\s+)((10|11)\.0|11\.1|11\.5|12\.(0|1)))|(SamsungBrowser\/((4|5)\.0|5\.4))|(IEMobile[ /](10|11)\.0)|(Android Eclair)|(Android Froyo)|(Android Gingerbread)|(Android Honeycomb)|(PlayBook.+RIM Tablet OS (7\.0|10\.0)\.\d+)|((Black[bB]erry|BB10).+Version\/(7\.0|10\.0)\.\d+)|(Trident\/6\.0)|(Trident\/5\.0)|(Trident\/4\.0)|(([MS]?IE) (5\.5|([6-9]|10)\.0))/.test(
      e
    ) &&
      // cookie๋ฅผ ๊ฐ€์ง€๊ณ  ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€์— ๋Œ€ํ•ด์„œ ํ…Œ์ŠคํŠธ
      window.navigator.cookieEnabled &&
      (!window.document.documentMode ||
        ((document.cookie = "ch-session-test=1"),
        document.cookie.split("ch-session-test=").length >= 2 &&
          ((document.cookie = "ch-session-test=; Max-Age=0"), 1))))
  ) {
    // ch-plugin Element๊ฐ€ ์—†๋‹ค๋ฉด ํ•ด๋‹น Element ๋งŒ๋“ค์–ด์„œ body์— ์‚ฝ์ž…
    if (!document.getElementById("ch-plugin")) {
      var i = document.createElement("div")
      ;(i.id = "ch-plugin"), document.body.appendChild(i)
    }
    // ch-plugin Element์— ๋ Œ๋”๋ง์— ํ•„์š”ํ•œ ํ•„์ˆ˜ Element์™€ Iframe ๊ฐ์ฒด ์‚ฝ์ž…
    document.getElementById("ch-plugin").innerHTML +=
      '<div id="ch-plugin-core"></div><div id="ch-plugin-script" style="display:none"><iframe id="ch-plugin-script-iframe" style="position:relative!important;height:100%;width:100%!important;border:none!important;"></iframe></div>'
    // ์œ„์—์„œ ์ƒ์„ฑํ•œ ch-plugin-script-iframe Element๋ฅผ n์— ์ €์žฅ
    var n = document.getElementById("ch-plugin-script-iframe"),
      // flag ์šฉ t false๋กœ ์ดˆ๊ธฐํ™”
      t = !1,
      // iframe ์ดˆ๊ธฐํ™” ์ž‘์—…์„ ํ•˜๋Š” ํ•จ์ˆ˜ o ์„ ์–ธ
      o = function () {
        // ch-plugin-script-iframe์˜ ๋‚ด๋ถ€์˜ document ๊ฐ์ฒด๋ฅผ e์— ์ €์žฅ
        var e = n.contentDocument || n.contentWindow.document
        // iframe ๋‚ด๋ถ€๋ฅผ writing ๋ชจ๋“œ๋กœ ์˜คํ”ˆ
        e.open(),
          // ํ•ต์‹ฌ ๊ธฐ๋Šฅ์ธ `https://cdn.channel.io/plugin/ch-plugin-core-20210923190001.js` script ์„ค์ •
          e.write(
            '<script async type="text/javascript" src="https://cdn.channel.io/plugin/ch-plugin-core-20210923190001.js" charset="UTF-8"></script>'
          ),
          // React Rendering๋ฅผ ์œ„ํ•œ ํ•„์ˆ˜ HTML ์‚ฝ์ž…
          e.write(
            '<!DOCTYPE html><html><head><meta charset="utf-8"></head><body><div id="main"></div></body></html>'
          ),
          // iframe ๋‚ด๋ถ€ writing ์ž‘์„ฑ ์™„๋ฃŒ
          e.close(),
          // flag ture๋กœ ์„ค์ •
          (t = !0)
      }
    // Document๊ฐ€ onload์‹œ iframe ์ดˆ๊ธฐํ™” ์ž‘์—… ์‹คํ–‰.
    n.onload || o(),
      (n.onload = function () {
        t || o()
      })
  }
})()

์ด์ œ ์—ฌ๊ธฐ์„œ iframe ๋ฅผ ์ดˆ๊ธฐํ™” ํ•˜๋Š” ์ž‘์—…์„ ์ง„ํ–‰ ํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ์—ฌ๊ธฐ์„œ ์ฃผ์š”ํ•˜๊ฒŒ ๋ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„์€ CORS ์™€ browser ํ˜ธํ™˜์„ฑ ์„ ๊ณ ๋ คํ•˜์—ฌ iframe์˜ src ๋ฅผ ํ†ตํ•ด ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ ์‚ฌ์ดํŠธ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด ์•„๋‹Œ ๋ฒˆ๋“ค๋ง๋œ script๋ฅผ ์ด์šฉํ•˜์—ฌ ์ง์ ‘ SPA๋ฅผ ๋ Œ๋”๋ง ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

์œ„์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ iframe๋ฅผ ๊ด€๋ฆฌํ•˜๊ฒŒ ๋˜๋ฉด CORS์˜ ์ •์ฑ…์— ๋”ฐ๋ฅธ script api access ์ œํ•œ์ด ์—†์–ด์ง€๋ฉฐ ๋˜ํ•œ CORS์— ๋”ฐ๋ฅธ X-Frame-Options header๋ฅผ ์„œ๋ฒ„ ๋‹จ์— ์„ค์ •ํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ์†Œ๋ชจ๋ฅผ ์ค„ ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#cross-origin_script_api_access

์ด์ œ ํ•œ๋ฒˆ ๋ฒˆ๋“ค๋ง๋œ script๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ดํŽด ๋ด…๋‹ˆ๋‹ค!

์ผ๋‹จ https://cdn.channel.io/plugin/ch-plugin-core-20210923190001.js ํŒŒ์ผ์„ ๋‹ค์šด ํ›„ minify์™€ ๋‚œ๋…ํ™”๊ฐ€ ์ผ๋ถ€๋ถ„ ์ ์šฉ์ด ๋œ ์ƒํƒœ์ด๋ฏ€๋กœ formatter์™€ ํŠน์ • object์˜ key๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ํ˜•ํƒœ๋กœ ๋ถ„์„์„ ํ•ด๋ด…๋‹ˆ๋‹ค!

์ผ๋‹จ ๊ฐ€์žฅ ๋จผ์ € ๋ณด์ด๊ฒŒ ๋˜๋Š” ๋ถ€๋ถ„์€ ์•„๋ž˜์™€ ๊ฐ™์ด ์ฃผ์„์„ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ ๋ผ์ด์„ ์Šค์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ ํ•ด๋‹น ํŒŒ์ผ์„ ํ™•์ธ ํ•˜๋ฉด ๋‚ด๋ถ€์—๋Š” react ์™€ ํ•จ๊ป˜ ์—ฌ๋Ÿฌ๊ฐ€์ง€์˜ ๋ถ€๊ฐ€์ ์ธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

/*! For license information please see ch-plugin-core-20210923190001.js.LICENSE.txt */

// ch-plugin-core-20210923190001.js.LICENSE.txt

...

/** @license React v16.13.0
 * react.production.min.js
 *
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

...

์ผ๋‹จ ํ•ด๋‹น ๊ธฐ์ˆ  ์Šคํƒ์€ react ๋ผ๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์œผ๋‹ˆ window.ChannelIO ๊ฐ์ฒด์— ๋Œ€ํ•œ ์ •์˜ ํ•˜๋Š” ๋ถ€๋ถ„์„ ์‚ดํŽด ๋ด…๋‹ˆ๋‹ค!

// ch-plugin-core-20210923190001.js

...
var n = yl();
(n.ChannelIO = n.ChannelIO || {}),
  (this.commandQueue = n.ChannelIO.q || []),
  (this.pendingQueue = []),
  (this.callbacks = {}),
  (this.methods = Ch(
    {
      show: function () {
        if (!t.pushQueue({ apiName: "show", apiArray: ["show"] }))
          if ((tl(nl("show", "showMessenger")), Ip)) {
            var e = Af.channelSelectors.getChannel(t.getState());
            wp.redirectToEdgeBrowser(e);
          } else
            t.dispatch(
              Tr.uiActions.setShowMessenger({ showMessenger: !0 })
            );
      },
      hide: function () {
        t.pushQueue({ apiName: "hide", apiArray: ["hide"] }) ||
          (tl(nl("hide", "hideMessenger")),
          t.dispatch(
            Tr.uiActions.setShowMessenger({ showMessenger: !1 })
          ));
      },
      lounge: function () {
        if (
          !t.pushQueue({ apiName: "lounge", apiArray: ["lounge"] })
        ) {
          tl(nl("lounge"));
          var e = Xf.getHistory();
          if ("/lounge" !== e.location.pathname)
            if (Af.uiSelectors.showMessenger(t.getState()))
              e.push("/lounge");
            else if (Ip) {
              var n = Af.channelSelectors.getChannel(t.getState());
              wp.redirectToEdgeBrowser(n);
            } else
              t.dispatch(
                Tr.uiActions.setShowMessenger({
                  showMessenger: !0,
                  disableRedirect: !0,
                })
              ),
                e.push("/lounge");
        }
      },
      boot: function (e) {
        var n =
          arguments.length > 1 && void 0 !== arguments[1]
            ? arguments[1]
            : Mn.a;
        if (!Tn()(n) || !Sn()(e))
          return (
            tl("boot failed: Invalid arguments."), On.a.resolve()
          );
        t.methods.shutdown(), Uf.setSettings(e);
        var r = Uf.getPluginKey();
        if (!r)
          return (
            tl("boot failed: pluginKey not found."), On.a.resolve()
          );
        var o = Uf.getLanguage(),
          i = Uf.getLanguageOverrided(),
          a = Uf.getBootOption();
        return "YOUR_USER_ID" === a.memberId
          ? (tl("boot failed: Invalid memberId"), On.a.resolve())
          : (o && t.dispatch(Tr.geoActions.setLocale({ locale: o })),
            i &&
              (bh.setLanguage(i),
              Mp && t.methods.updateUser({ language: i })),
            t
              .dispatch(
                Tr.pluginActions.requestCheckIn({
                  pluginKey: r,
                  option: a,
                })
              )
              .promise.then(function (e) {
                var r = wn()(e, "payload.user", {});
                n(null, r),
                  _n()(t.pendingQueue) ||
                    t.execQueue(t.pendingQueue).then(function () {
                      t.pendingQueue = [];
                    });
              })
              .catch(function (e) {
                var t = wn()(
                  e,
                  "payload.body",
                  new Error("boot failed")
                );
                n(t, null);
              }));
      },
      shutdown: function () {
        t.dispatch(Tr.pluginActions.checkOut({}));
      }
...

์ฝ”๋“œ์˜ ์–‘์ด ๋ฐฉ๋Œ€ ํ•จ์œผ๋กœ ์ผ๋ถ€๋ถ„๋งŒ ํ™•์ธ์„ ํ•ด๋ณด๋‹ˆ window.ChannelIO ์— ์™ธ๋ถ€์—์„œ channel.io ์™€ ์ƒํ˜ธ ์ž‘์šฉ์ด ๊ฐ€๋Šฅํ•œ ํ•จ์ˆ˜๋“ค์„ ์ •์˜ ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ dispatch, actions, dispatch chaining promise ๋ฅผ ํ†ตํ•ด ์ „์—ญ์œผ๋กœ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ redux, redux-saga ์™€ ๊ฐ™์€ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉ ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฒˆ์—๋Š” https:// ์™€ ๊ฐ™์€ ํ‚ค์›Œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์–ด๋””์™€ ํ†ต์‹ ์„ ์ฃผ๊ณ  ๋ฐ›๋Š”์ง€ ๋ถ„์„์„ ํ•ด๋ด…๋‹ˆ๋‹ค.

...
/**
 * https://api.channel.io์— ๋Œ€ํ•œ ์š”์ฒญ์„ ์‹คํ–‰ ํ•˜๋Š” ํ•จ์ˆ˜ ์ •์˜ ๋ถ€๋ถ„!
 * credentials: "omit" ๋ฅผ ํ†ตํ•ด fetch API๋ผ๋Š” ๊ฒƒ์„ ์œ ์ถ” ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
 */
function n() {
  var e;
  return (
    he()(this, n),
    (e = t.call(this, "https://api.channel.io", {
      credentials: "omit",
    })),
    ve()(bu()(e), "language", Uf.getLanguageOverrided()),
    e
  );
}
...
/**
 * https://browser.sentry-cdn.com/5.6.2/bundle.min.js ๋ฅผ ํ†ตํ•ด
 * sentry๋ฅผ ์ด์šฉํ•˜์—ฌ ํ”„๋ก ํŠธ ์—๋Ÿฌ ๋กœ๊ทธ๋ฅผ ์ˆ˜์ง‘ ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
 */
key: "install",
  value: function () {
    var e = window.document.createElement("script");
    (e.onload = function () {
      var e, t;
      null === (e = window.Sentry) ||
        void 0 === e ||
        e.init({
        dsn: "https://[email protected]/306179",
        environment: In()(
          (t = "".concat("production", "|"))
        ).call(t, "App"),
        release: "7.0.5",
        whitelistUrls: [/cdn\.channel\.io/, /cdn\.ravenjs\.com/],
        normalizeDepth: 5,
      });
    }),
      (e.async = !0),
      (e.crossOrigin = "anonymous"),
      (e.integrity =
       "sha384-H4chu/XQ3ztniOYTpWo+kwec6yx3KQutpNkHiKyeY05XCZwCSap7KSwahg16pzJo"),
      (e.src =
       "https://browser.sentry-cdn.com/5.6.2/bundle.min.js"),
      window.document.head.appendChild(e);
  },
...
/**
 * ๋˜ํ•œ msg์™€ ๊ฐ™์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฃผ๊ณ  ๋ฐ›๊ธฐ ์œ„ํ•œ websocket๋„ ์„ค์ • ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!
 */
{
  key: "connect",
  value: function () {
    var e = this;
    this.isConnected() && this.disconnect({ forceDisconnect: !0 }),
      Zc(),
      (this.connecting = !0),
      (this.forceDisconnect = !1),
      (this.ready = !1),
      (this.readyQueue = []),
      (this.socket = (0, v_.a)("https://ws.channel.io/front-v4", {
      transports: ["websocket"],
      upgrade: !0,
      reconnectionDelay: 5e3,
      reconnectionDelayMax: 1e4,
    })),
      this.socket.on("ready", this.onReady),
      this.socket.on("connect", this.onConnect),
      this.socket.on("connect_error", this.onConnectError),
      this.socket.on("connect_timeout", this.onConnectTimeout),
      this.socket.on("reconnect_error", this.onReconnectError),
      this.socket.on("reconnect_failed", this.onReconnectFailed),
      this.socket.on("reconnect_attempt", this.onReconnectAttempt),
      this.socket.on("reconnect", this.onReconnect),
      this.socket.on("error", this.onError),
      this.socket.on("disconnect", this.onDisconnect),
      window.addEventListener("offline", function () {
      Zc(),
        e.isConnected() &&
        (Zc(), e.disconnect({ forceDisconnect: !1 }));
    }),
      window.addEventListener("beforeunload", function () {
      Zc(), e.disconnect({ forceDisconnect: !0 });
    });
  },
},
...

์œ„์— ์ ์€ ๋ถ€๋ถ„ ๋ง๊ณ ๋„ ์—ฌ๋Ÿฌ๊ฐ€์ง€ Error ์— ๋Œ€ํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ํ†ตํ•ด redux, styled-components, core-decorators, date-fns ๋“ฑ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ ๋Œ€๋žต์ ์œผ๋กœ ์–ด๋–ค ํ˜•ํƒœ๋กœ ๋Œ์•„ ๊ฐ€๋Š” ์ง€์— ๋Œ€ํ•ด์„œ ํ™•์ธ์„ ํ–ˆ์œผ๋‹ˆ ์ถ”๊ฐ€์ ์œผ๋กœ ์‚ฌ์šฉํ•œ ๊ธฐ์ˆ  ์Šคํƒ์— ๋Œ€ํ•ด์„œ ์กฐ์‚ฌ๋ฅผ ํ•ด๋ด…๋‹ˆ๋‹ค!

โš™๏ธ ๊ธฐ์ˆ  ์Šคํƒ ์กฐ์‚ฌํ•˜๊ธฐ!

์ผ๋‹จ ๊ธฐ์ˆ  ์Šคํƒ์— ์กฐ์‚ฌํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์กด์žฌ ํ•˜๋Š”๋ฐ ๊ฐ€์žฅ ์ผ๋‹จ ํ•ด๋‹น ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ๋‚˜ ์ฑ„์šฉ ์‚ฌ์ดํŠธ์— ๋“ค์–ด๊ฐ€์„œ ๊ธฐ์ˆ  ์Šคํƒ์— ๋Œ€ํ•ด์„œ ์กฐ์‚ฌ๋ฅผ ํ•ด๋ด…๋‹ˆ๋‹ค.

image-20210930031153869

image-20210930031553462

image-20210930031433849

์ฑ„์šฉ๊ณต๊ณ ๋ฅผ ํ™•์ธ ํ•ด ๋ณด๋‹ˆ ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉ ํ•˜๋Š” ๊ธฐ์ˆ  ์Šคํƒ์— ๋Œ€ํ•ด์„œ ์ž์„ธํžˆ ์„ค๋ช…์„ ํ•˜๊ณ  stackshare ๋ฅผ ํ†ตํ•ด ์ž์„ธํ•˜๊ฒŒ ๊ณต์œ  ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

์•„๋ž˜์˜ ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ์—์„œ ์„ค๋ช…ํ•˜๋Š” frontend-stack ๊ธ€์„ ํ†ตํ•ด window์™€ macos์˜ native app๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด electron ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ์ •๋ณด๋ฅผ ์ถ”๊ฐ€๋กœ ์–ป์„ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค!

https://channel.io/ko/blog/frontend-stack

image-20210930033226497

์ด๋ฒˆ์—๋Š” ๋”์šฑ๋” ์ƒ์„ธํ•˜๊ฒŒ ์„ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์–ด๋–ค ๊ฒƒ์„ ์‚ฌ์šฉ ํ•˜๋Š”์ง€์— ๋Œ€ํ•ด ํ™•์ธ์„ ํ•˜๋„๋ก ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด์„ ์Šค ์‚ฌ์ดํŠธ์— ๋“ค์–ด๊ฐ€ ํ™•์ธ์„ ํ•ด๋ด…๋‹ˆ๋‹ค.!

https://channel.io/ko/oss

image-20210930032327345

ํ•ด๋‹น ๋ผ์ด์„ ์Šค ๋ฌธ์„œ๋ฅผ ํ†ตํ•ด ์„ธ๋ถ€ ์ ์œผ๋กœ ์‚ฌ์šฉํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์‰ฝ๊ฒŒ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

๋งˆ์ง€๋ง‰์œผ๋กœ wappalyzer ์™€ ๊ฐ™์€ ๋ถ„์„ ๋„๊ตฌ๋ฅผ ์ด์šฉํ•ด์„œ ๋ถ„์„์„ ํ•ด๋ด…๋‹ˆ๋‹ค.

์ผ๋‹จ iframe ๋‚ด๋ถ€๋Š” ๋ถ„์„์„ ์ œ๋Œ€๋กœ ํ•˜์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์— channel.io ์˜ ๋ฉ”์ธ ํ™ˆํŽ˜์ด์ง€๋ฅผ ๋ถ„์„์„ ํ•ด๋ด…๋‹ˆ๋‹ค.!

image-20210930032645845

์œ„์™€ ๊ฐ™์ด ํ™ˆํŽ˜์ด์ง€์˜ ๊ฒฝ์šฐ Next.js ๋ฅผ ์ด์šฉํ•˜์—ฌ SSR ๋ฅผ ์ œ๊ณต ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“– ๋ถ„์„ ํ•ด๋ณด๋ฉด์„œ ์•Œ๊ฒŒ๋œ ์‚ฌ์‹ค!

๊ธฐ์กด ์ด๋Ÿฐ ํŠน์ • ์„œ๋น„์Šค ์œ„์—์„œ ๋Œ์•„๊ฐ€๊ฒŒ ๋˜๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ ์„œ๋น„์Šค๋Š” iframe ์œ„์—์„œ ๋™์ž‘์„ ํ•˜๊ฒŒ ๋œ๋‹ค๋ผ๊ณ  ์•Œ๊ณ  ์žˆ์—ˆ๋Š”๋ฐ ์™œ ๊ทธ๋Ÿฌํ•œ ๋ฐฉ์‹์„ ์ฑ„ํƒ ํ•˜๋Š”์ง€์— ๋Œ€ํ•ด์„œ๋Š” ์ž์„ธํžˆ ๋ชจ๋ฅด๋Š” ๋ถ€๋ถ„์ด ์žˆ์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ์— ๋ถ„์„์„ ํ†ตํ•œ ํ•™์Šต์„ ํ•˜๋ฉด์„œ iframe ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด namespace , css scope ๋“ฑ์ด ๋ถ„๋ฆฌ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ฑ„ํƒ์„ ํ•œ๋‹ค๋Š” ๊ฒƒ๊ณผ iframe ์˜ cors ์ •์ฑ…์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด script ๋ฅผ ์ง์ ‘ iframe ๋‚ด๋ถ€์— ์‚ฝ์ž… ํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์— ๋Œ€ํ•œ ํ–ฅ์ƒ์„ ์œ„ํ•ด SPA ํ˜•ํƒœ๋กœ ์ œ๊ณต์„ ํ•˜๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!

๊ฒฐ๋ก  : ํ”Œ๋Ÿฌ๊ทธ์ธ ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ธŒ๋ผ์šฐ์ €์˜ ์ž‘๋™๋ฐฉ์‹๊ณผ ํ˜ธํ™˜์„ฑ์— ๋Œ€ํ•ด์„œ ๊นŠ๊ฒŒ ๊ณต๋ถ€๋ฅผ ํ•ด์•ผ ํ•˜๊ณ , ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์œ„ํ•ด SPA ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค!

Buy me a coffeeBuy me a coffee
Written by

@JaeSeoKim

๋ณด์•ˆ๊ณผ ๊ฐœ๋ฐœ์„ ์ข‹์•„ํ•˜๋Š” ํ•™์ƒ ์ž…๋‹ˆ๋‹ค~!