코딩 요정 버터링

next.js (react.js) 에서 채널톡 커스텀 훅으로 만들어 사용하기 본문

프론트엔드 개발

next.js (react.js) 에서 채널톡 커스텀 훅으로 만들어 사용하기

butter-ring 2023. 5. 11. 22:01

회사에서 맡아 운영하는 서비스에서 채널톡을 도입하였다.

공식문서에 보면 자바스크립트로 밖에 되어있지 않아서 도입을 하려면 커스텀훅을 사용해서 도입해야했다.

구글링을 했지만 딱히 뭔가 확 오는 방법이 없어서 직접 커스텀훅을 만들기로 결정!

 

ts 파일을 hook 디랙토리에 생성 후 일단 공식 문서에 있는 인터페이스들을 긁어왔다.

그 후 스크립트를 function으로 작성 후 채널톡에서 제공하는 기본 함수들을 타입스크립트로 변경하여 적용하였다.

 

declare global {
  interface Window {
    ChannelIO?: IChannelIO;
    ChannelIOInitialized?: boolean;
  }
}

interface IChannelIO {
  c?: (...args: any) => void;
  q?: [methodName: string, ...args: any[]][];
  (...args: any): void;
}

interface BootOption {
  appearance?: string;
  customLauncherSelector?: string;
  hideChannelButtonOnBoot?: boolean;
  hidePopup?: boolean;
  language?: string;
  memberHash?: string;
  memberId?: string;
  mobileMessengerMode?: string;
  pluginKey: string;
  profile?: Profile;
  trackDefaultEvent?: boolean;
  trackUtmSource?: boolean;
  unsubscribe?: boolean;
  unsubscribeEmail?: boolean;
  unsubscribeTexting?: boolean;
  zIndex?: number;
}

interface Callback {
  (error: Error | null, user: CallbackUser | null): void;
}

interface CallbackUser {
  alert: number;
  avatarUrl: string;
  id: string;
  language: string;
  memberId: string;
  name?: string;
  profile?: Profile | null;
  tags?: string[] | null;
  unsubscribeEmail: boolean;
  unsubscribeTexting: boolean;
}

interface UpdateUserInfo {
  language?: string;
  profile?: Profile | null;
  profileOnce?: Profile;
  tags?: string[] | null;
  unsubscribeEmail?: boolean;
  unsubscribeTexting?: boolean;
}

interface Profile {
  [key: string]: string | number | boolean | null;
}

interface FollowUpProfile {
  name?: string | null;
  mobileNumber?: string | null;
  email?: string | null;
}

interface EventProperty {
  [key: string]: string | number | boolean | null;
}

type Appearance = 'light' | 'dark' | 'system' | null;

export default function UseChannelService() {
  const loadScript = () => {
    let w = window;
    if (w.ChannelIO) {
      return (window.console.error || window.console.log || function () {})('ChannelIO script included twice.');
    }
    const ch: IChannelIO = function () {
      ch.c?.(arguments);
    };
    ch.q = [];
    ch.c = function (args) {
      ch.q?.push(args);
    };
    w.ChannelIO = ch;
    function l() {
      if (w.ChannelIOInitialized) {
        return;
      }
      w.ChannelIOInitialized = true;
      let s = document.createElement('script');
      s.type = 'text/javascript';
      s.async = true;
      s.src = 'https://cdn.channel.io/plugin/ch-plugin-web.js';
      let x = document.getElementsByTagName('script')[0];
      if (x.parentNode) {
        x.parentNode.insertBefore(s, x);
      }
    }
    if (document.readyState === 'complete') {
      l();
    } else {
      w.addEventListener('DOMContentLoaded', l);
      w.addEventListener('load', l);
    }
  };

  loadScript();

  return () => {
    window.ChannelIO?.('clearCallbacks');
  };
}

export const ChannelTalk = {
  boot: (option: BootOption, callback?: Callback) => {
    window.ChannelIO?.('boot', option, callback);
  },

  shutdown: () => {
    window.ChannelIO?.('shutdown');
  },

  showMessenger: () => {
    window.ChannelIO?.('showMessenger');
  },

  hideMessenger: () => {
    window.ChannelIO?.('hideMessenger');
  },

  openChat: (chatId?: string | number, message?: string) => {
    window.ChannelIO?.('openChat', chatId, message);
  },

  track: (eventName: string, eventProperty?: EventProperty) => {
    window.ChannelIO?.('track', eventName, eventProperty);
  },

  onShowMessenger: (callback: () => void) => {
    window.ChannelIO?.('onShowMessenger', callback);
  },

  onHideMessenger: (callback: () => void) => {
    window.ChannelIO?.('onHideMessenger', callback);
  },

  onBadgeChanged: (callback: (alert: number) => void) => {
    window.ChannelIO?.('onBadgeChanged', callback);
  },

  onChatCreated: (callback: () => void) => {
    window.ChannelIO?.('onChatCreated', callback);
  },

  onFollowUpChanged: (callback: (profile: FollowUpProfile) => void) => {
    window.ChannelIO?.('onFollowUpChanged', callback);
  },

  onUrlClicked: (callback: (url: string) => void) => {
    window.ChannelIO?.('onUrlClicked', callback);
  },

  updateUser: (userInfo: UpdateUserInfo, callback?: Callback) => {
    window.ChannelIO?.('updateUser', userInfo, callback);
  },

  addTags: (tags: string[], callback?: Callback) => {
    window.ChannelIO?.('addTags', tags, callback);
  },

  removeTags: (tags: string[], callback?: Callback) => {
    window.ChannelIO?.('removeTags', tags, callback);
  },

  setPage: (page: string) => {
    window.ChannelIO?.('setPage', page);
  },

  resetPage: () => {
    window.ChannelIO?.('resetPage');
  },

  showChannelButton: () => {
    window.ChannelIO?.('showChannelButton');
  },

  hideChannelButton: () => {
    window.ChannelIO?.('hideChannelButton');
  },

  setAppearance: (appearance: Appearance) => {
    window.ChannelIO?.('setAppearance', appearance);
  },
};

 

 

처음엔 nav에서 채널톡을 실행하였지만 그러다 보니 유저의 정보를 업데이트 할 때 채널톡을 두번 호출하는 에러가 있었다. 

채널톡을 사용하고 서비스를 운영하는데 크리티컬한 에러는 아니였지만 맘에 들지 않았기 때문에

_app.ts 에 useEffect로 웹 어플리케이션 실행 시 딱 한번만 커스텀 훅이 실행되게 코드를 작성하였다.

 

const MyApp = (props: MyAppProps) => {
  const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;

  useEffect(() => {
    UseChannelService();
  }, []);

  return (
    <>
      <Meta />
      <CacheProvider value={emotionCache}>
        <ThemeProvider theme={theme}>
          {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
          <CssBaseline />
          <GlobalStyles
            styles={{
              body: { backgroundColor: '#1E1E1E' },
            }}
          />
          <Layout>
            <Component {...pageProps} />
          </Layout>
        </ThemeProvider>
      </CacheProvider>
    </>
  );
};

export default appWithTranslation(MyApp);

사용자가 웹 페이지에 접속했을때 커스텀 훅을 실행시켜 채널톡에 연동한 후 각 함수가 필요한 페이지나 컴포넌트에서 

호출하여 쓰니 오류가 해결~~!!!

 

내가 사용한 방식이 옳은 방식인진 잘 모르겠지만 혹시라도 필요한 분들이 계시면....

 

 

Comments