brunch

매거진 airdesk

You can make anything
by writing

C.S.Lewis

by 한상훈 Jul 06. 2018

구글 캘린더 연동하기

에어데스크 개발일지

나는 에어데스크라는 확장 프로그램을 만들고 있다. 브라우저에 대시보드를 만들어주는 편리한 애플리케이션이다. 그 중에서 핵심인 캘린더 기능을 발전시키고자 하는데, 이것이 사용자들이 가장 필요로 하는 기능이기 때문이다.


구글 캘린더 정복은 쉬운 일이 아니었다.

글 제목은 1일차라고 적었지만, 실제론 200일 이상의 전투를 이미 지나왔다.

군복무 중에도 수차례 시도했으나 API 문서에 접속하는 것 자체가 너무 느리고, 아예 접속이 안 되는 환경이라 진행이 제자리 걸음일 수 밖에 없었다. 그러나 캘린더를 쓰고, 편집하기 위해선 API를 사용해야하는데, 이걸 잊기보다는 도리어 적어가면서 이 문제를 정면돌파 해보고자 한다.




1일차

첫번째 시도

공식 가이드 북을 따라서 진행해보았다. 먼저 Browser 소스 코드다.

이 코드는 HTML과 자바스크립트로 구성되어있기에 가장 쉽게 이해할 수 있었다.

그러나 문제는 확장 프로그램에서 작동하지 않는다는 점이다.


대강의 순서는 이렇다.

1. OAuth 클라이언트 아이디를 만들고, API Key를 만들어서 client_secret.json 파일로 다운받는다.

2. index.html파일의 ID와 Key부분만 수정해서 같은 폴더에 위치시킨다.

3. 파이썬 명령으로 실행시킨다.


그런데 확장 프로그램에선 파이썬 명령으로 실행시킬 수 없이 클라이언트 코드자체로 구동되어야 하기 때문에 안의 자바스크립트 코드만따서 진행해보았다.


첫번째 에러가 발생했는데 확장 프로그램은 보안상의 이유로 인라인 스크립트를 막고있다. 인라인 스크립트라함은 HTML에 자바스크립트 코드를 직접 쓰는걸 뜻한다. 해결책은 js파일로 만들어서 참조하면된다. 어렵지 않기에 참조해보았으나 새로운 문제가 발생한다.


참조 코드 중에선 구글 API를 요청하는 코드가 있다. 이러한 외부 링크로 인한 참조 문제를 해결하기 위해선 두가지 방법 중 하나면 보통 해결된다. 매니페스트 파일에 퍼미션을 추가해주거나, content security policy에 인라인 형태로 주소를 추가해주는 방법이다. 구글 API 같은 라이브러리를 가져올 때는 content security policy에 추가하면 에러가 해결된다.


에러는 잡았으나 새로운 문제를 발견했다. 바로 srcript 태그 안의 인라인 코드이다.

<script async defer src="https://apis.google.com/js/api.js"
      onload="this.onload=function(){};handleClientLoad()"
      onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>


onload와 onreadystatechange 이벤트가 잡혀있는데 이를 해결할 방법을 모르겠다. 이 부분까지 함께 js파일 안으로 들어가야 정상적으로 작동하게 된다. 그러나 문제는 내가 이 코드의 의미를 이해하지 못했다는 점이다.


이를 해결하지 못해서 코드 동작만이라도 보기 위해 여러 삽질을 했다.


두번째 시도

C9 IDE를 이용해서 같은 코드를 동작시켜보았다. 파이썬 명령을 내리자 로컬호스트에 대기명령을 내리게 되는데, C9의 개발용 주소로 접속해보니 서버가 동작중이지 않았다.


세번째 시도

컴퓨터에 파이썬을 설치해 작동시켜보았다. 동일한 코드임에도 인코딩 에러가 발생했다. UTF-8에러 문제인데, 검색해보아도 어떻게 해결해야할지 감조차 잡을 수 없었다.


네번째 시도

구글 앱 스크립트를 사용했다. 이건 정말 쉽고, 심지어 내가 할 일도 없다. 그렇기에 작동은 확인할 수 있었으나 내 프로그램에 적용할 방법이 막막했다. 앱 스크립트를 활용하려면 모든 코드를 앱 스크립트에 업로드해서 새롭게 시작해야하는데, 이 작동방식이 웨일 브라우저에서도 작동되는지 불확실했다. 또한 앱 스크립트라는걸 오늘 처음써보다 보니 너무 막연했다. 본능적으로 소스 파일에 넣을 수 없는 해결책이었기에 계속 진행하지 않았다.


다섯번째 시도

다시 최초의 코드로 돌아왔다. 소스코드도 그냥 로컬 파일로 모두 만들어보았다. 이러면 콘텐츠 보안 정책에 걸리지 않는다는 장점이 있다. 그러나 여전히 onload이후의 인라인 스크립트 문제를 해결할 방법이 없었다. 압축된 구글 API를 손수 엔터를 눌러가며 onload 이벤트의 실마리를 잡아보려고 했다. 그러나 압축된 상태를 역으로 푸는 것도 우스운 일이었지만, 정작 this.onload = function() {}의 의미를 모르기에 결국 의미없는 삽질이었다.


지금도 의문인건 this.onload = function () {}이 무슨 의미인걸까? 온로드되면, 온로드 이벤트를 텅텅 비우라는 뜻일까?


더 의문인건 그 다음 onreadystatechange이다. 검색해보니 ajax 명령이라고 하는데, ajax는 이미 에어데스크에서도 사용하고 있기에 어떤 개념인지는 알고 있다. 그러나 API를 참조하고 있는 스크립트 태그에 있는 onreadystatechange는 무슨 의미인지 도통 알 수 없었다. 그리고 this는 뭘 뜻하는걸까.


여섯번째 시도

콘텐츠 보안 정책에 대해 더 깊게 들어가 보았다. 태그 안의 인라인 코드를 처리하지 못하는 상황이기 때문이다. Unsafe inline 코드를 사용해보았지만 확장 프로그램의 보안 정책상 사용할 수 없는 것으로 보인다.


지금은 하루가 지나 새벽 1시가 넘었다. 캘린더를 해결해야 다음 API와 더 많은 서비스로 넘어갈 수 있기에 뒤로 물러나고 싶진 않은데, 하루종일 한 것치곤 코드 한 줄에 지는 것 같아 마음이 좋진 않다.




2일차

오늘은 7월 9일 월요일 새벽 0시 49분.

새로운 아이디어가 떠올라 지난 몇시간동안 이것저것 시도해보았다. 나쁘지 않은 성과일 수 있으나 여전히 오리무중이다.


일곱번째 시도

뜻하지 않게 웨일 브라우저 정보를 찾아보다가 웨일에선 구글 로그인이 작동하지 않는다는 사실을 발견했다. 물론 옛날 자료일 수 있으나 아직까지 웨일에서만 테스트를 해봤기에 크롬으로 바꿔서 시도해보는건 어렵지 않은 일이었다. 결과는 실패.


여덟번째 시도

관점을 바꾸었다. 태그를 생성하는 방법을 사용해보았다.

이 방법은 확장 프로그램에서 내부 폰트 파일을 가져올 때 사용한 방법이다. 확장 프로그램의 엄격한 보안 규칙 때문에 폰트를 가져오는 스타일 태그를 보안상 문제! 퉤! 하고 뱉어버린다. 그래서 html태그가 로드된 이후에 다시 동적으로 태그를 생성하는 방법으로 처리하니 문제를 해결했다.


이 방법을 구글 캘린더 코드에도 동일하게 적용해보았다.

문제는 태그를 생성하긴 했으나 onload 명령과 onreadychange명령은 감지하지 못했다.


아이러니한건 결과값이다.

강제로 onload 명령을 실행시켜보면 로그인 필요(login required)를 포함한 객체를 리턴한다. 객체에 있는 url을 열어보면, 내 일정 정보가 나온다. 즉 로그인은 실패했으나 일정은 가져오는 현상다.


아홉번째 시도

이보다 심플하게 jQuery 태그 생성 방법을 이용해보았다. 태그를 생성하고, 강제로 onload 트리거를 발생시키는 방법이다. 아쉽게도 실패.


열번째 시도

문제의 태그를 제외한 코드를 살펴봤다. 명령 순서에 따라 어떤 기능을 하는지 살펴봤다. 간단했다.

구글 API를 로드하면, 클라이언트 ID와 API키를 넣어서 가동시킨다. 문제는 가동시키는 init 메소드에서 프로세스가 멈춘다는 점이었다. 에러를 캐치해보려고 try, catch로 감싸봤지만 에러는 없다. Warning도 없다.


열한번째 시도

콘텐츠 스크립트 정책을 다시 보았다. 거기에 흥미로운 부분인 nonce(넌스)와 sha-를 이용한 방식이 가능해보였다. 인라인 스크립트 코드를 사용할 수 있게 해준다고 설명엔 나와있었으나 방법이 요원했다. 검색을 해보았으나 아직도 이게 뭘 의미하는지 모르겠다.


조금 찾은 정보에 의하면 넌스의 경우 재방문 감지에 사용되는데, 지금은 타임스템프로 대체되었다는 정보를 얻었다. sha방식은 암호화에 사용되는건 이해하겠는데 이걸 인라인 코드에 어떻게 적용한다는 것인지와 태그 안의 이벤트도 함께 암호화하여 전송하고, 실행할 수 있는지 방법을 몰랐다. 간단하게 인코딩을 해서 실행시켰으나 결과는 실패.


열두번째 시도

스크립트 태그에 ID를 부여하고, js파일을 통해서 이벤트를 부여했다. 이전에도 사용했던 방법이지만 좀 더 모양새를 갖추었다. 인증 명령을 내리면 역시 인증을 하지 않고, 끝났다. 문제는 gapi.init 메소드였다.


해당 메소드에 관련한 이슈를 찾아보았다.

두려운 결과였다. 확장 프로그램에서 호환하지 않는 것 같다는 내용이었다. 하지만 약간 안심했는데, 그래도 여기 글 올린 사람들과 같은 시야가 되었다는 느낌 때문이다.


열세번째 시도

문제가 되는 인증에 대해서 조사했다.

인증에 사용되는 OAuth2에 대한 샘플 파일은 확장 프로그램 가이드 문서에서 쉽게 찾을 수 있었다.

아직까지 모든 케이스는 일반적 브라우저 경우에 사용하는 방법이었다. 샘플코드는 파이썬을 이용해 간단한 웹서버를 구동하는 예제였기 때문이다. 파이썬 예제는 사용자 정보를 모두 js 파일 안에서 처리했다. 반면 위의 문서는 인증에 사용되는 정보를 매니페스트에 입력한다. 매우 흥미로운 부분이다.


문서가 꽤 긴데 지금은 새벽이 늦었으니 자고 일어나서 읽어야겠다. 해답을 찾은것 같다.





3일차

오늘은 7월 9일 오전 11시이다. 오늘 새벽에 발견한 설명서를 바탕으로 다시 작업을 시작했다. 예제의 전체 소스코드를 다운받고 설명을 따라 몇가지 수정을 했다. 그러던 중 놀라운 사실을 발견했다.


1. 인증을 위해선 크롬 앱 클라이언트 ID가 필요(그 전까진 웹 애플리케이션 타입으로 테스트했었다)

2. 웹스토어에 있는 공개키 필요(아직까지 왜 있는지도 몰랐던 공개키의 의미를 알게됨)

3. 웹스토어에 등록된 확장 프로그램 아이디 필요


필요한 값이 브라우저 인증 방법과 달랐다. 작동 메커니즘이 다르다는 것은 생각해볼 여지가 많았다.


위에서 요구한 3개의 값은 웨일 브라우저 호환을 어렵게 만든다. 웨일 스토어에선 공개키, 확장 프로그램 아이디가 없다. 크롬 앱 클라이언트 아이디를 발급받기 위해선 확장 프로그램 아이디를 입력해야하는데, 웨일 스토어에선 작동할 수 없는 일이다. 또한 이 이슈로 인해서 파이어폭스를 포함한 다른 브라우저 호환에 대해 한가지 장벽이 더 생긴 셈이다.


그래서 지금은 웨일 연구소에 질문글을 올리고, 테스트 앱을 게시해 테스트를 진행 중이다. 테스트가 성공적이라면 API 연동의 문을 연 셈이다. 그럼 7월 중순 안으로 목표를 달성할 수 있을 것 같다.


1시간의 등록시간이 지나고 테스트 앱을 사용해보았다.

위의 이미지처럼 인증 요청이 정상적으로 나타나고, 인증이 끝나면 주소록 API를 통해 등록된 사용자를 가져오는 모습이다.


반면 웨일에서는 예상처럼 작동하지 못했다. 에러 문구에 나오는 idenity APIs를 지원하지 않는다는 메세지는 인증을 지원하지 않는다는 뜻이고, 확장 프로그램 형태론 로그인이 어렵다는걸 말한다.


열네번째 시도

인증을 성공했으나 아직 핵심 기능이 남았다. 바로 어떻게 일정을 추가할 것인가? 라는 질문이다. 이 질문을 해결하기 위해서 공식 문서를 찾아보니 역시 gapi의 client 메소드를 이용해서 다시 인증 절차를 밟는 방법이 나타났다. 이 방법으로는 기존의 인증 방법과는 다르기 때문에 사용할 수 없었다.


해답은 cURL 방법이었다. 성공한 방법은 fetch를 사용해서 데이터를 가져오는 방식이었기에 cURL을 fetch로 바꾸어 입력해주면 되는 것이었다. 물론 cURL을 fetch로 바꾸는게 쉽진 않았다. cURL에 들어가는 data 부분을 fetch에서도 동일하게 data로 처리하거나, 또는 ajax에서 사용하는 resources 등으로 바꾸어도 동작하지 않았다.

계속해서 "Missing end time." 오류를 출력했다. 결국 cURL 명령 형태를 fetch 모양으로 제대로 바꾸어주면 문제는 해결된다는 뜻인데,  이를 해결할 사이트를 찾았다.

이 사이트를 통해서 cURL을 Fetch로 적절히 바꾸고 나서 작동시켜보니 드디어 오랫동안 꿈꾸던 일이 펼쳐졌다.


이벤트 생성에 성공했고, 또 캘린더로 당연히 가져오게 된다.


이제 정말 고지가 얼마 남지 않았다.

사실상 방법은 이제 알았으니 연동까지 자잘한 일들만하면 된다.


브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari