크롬 확장프로그램 개발⛏️ 회고

안녕하세요! 최근에 포털개발팀에서 Zum NewTab 이라는 크롬 확장프로그램을 만들었습니다. 4월부터 8월까지의 확장프로그램을 개발, 배포, 검수하는 과정에서의 삽질한 경험을 공유하고자 이렇게 글을 올립니다.


1. 프로젝트 개요

크롬 확장프로그램

확장프로그램은 사용자의 브라우징 경험을 긍정적으로 확장시킬 수 있는 작은 소프트웨어입니다. 이를 통해 사용자는 브라우저의 기능과 동작을 개인의 필요 또는 선호도에 맞게 조정할 수 있습니다.

확장프로그램 개발은 HTML, JavaScript 및 CSS와 같은 웹 기술을 기반으로 이루어지며, Chrome 개발자 대시 보드를 통해 배포할 수 있습니다. 이에 대한 내용은 뒤에서 자세하게 다루도록 하겠습니다.

배포가 완료되면 Chrome 웹 스토어에서 다운로드할 수 있습니다. 앱 개발과 많은 부분에서 유사합니다.

* 참고: https://developer.chrome.com/extensions

확장프로그램 조사

굉장히 다양한 성격의 확장프로그램이 많았고, 먼저 어떤 형태의 확장프로그램을 만들어야 좋을지 리서치를 했습니다.

리서치

여러가지 논의가 나왔고 바로가기 링크, 위젯, 웰페이퍼, 검생 등의 기능을 골고루 포함한 확장프로그램을 만들기로 결정되었습니다.


2. 프로젝트 결과물 소개

👉👉👉 확장프로그램(Zum NewTab) 다운로드 👈👈👈

먼저 결과물부터 간단하게 소개해드리겠습니다.

전체화면

결과물은 생각보다 이쁘게 만들어졌습니다 👏👏👏

  1. 날씨
    2-weather_1 2-weather_2
    • 현재 위치에 대한 기온, 대기상태, 미세먼지 농도 등을 보여줍니다.
    • 지역별 날씨를 한 눈에 볼 순 없지만 특정 위치에 대한 날씨는 조회할 수 있습니다.
  2. 시계
    3-clock
    • 현재 시각을 보여줍니다.
  3. 운세
    4-fortune
    • 띠별운세, 별자리운세, 개인운세 등을 조회할 수 있습니다.
    • 더보기는 검색줌과 연결되어 있습니다.
  4. 검색
    5-search
    • 줌, 네이버, 다음, 구글, 유튜브 등의 검색엔진으로 검색 가능합니다.
    • 기획에는 없지만 개인적으로 네이버처럼 키보드 입력시 바로 검색엔진에 커서가 가도록 하고 싶은데 생각만 하는 중입니다.
  5. 추천사이트, 자주방문한 사이트
    6-sites
    • 추천 사이트를 커스텀하여 관리할 수 있습니다.
    • 자주 방문하는 사이트가 자동으로 표시됩니다.
    • 개인적으로 제일 많이 사용하는 영역입니다.
  6. 주제별 컨텐츠
    7-contents
    • 주요뉴스, TV연예, 스포츠, 라이프, 여행/푸드 등의 컨텐츠를 조회할 수 있습니다.
    • 개인적으로 라이프, 여행/푸드에 올라오는 컨텐츠가 재미있어서 많이 보는 편입니다.
  7. 이슈검색어
    8-issueword
    • 실시간 이슈를 확인할 수 있습니다.
    • 사실 눈에 잘 띄지 않아서 UI 개선이 필요한 영역입니다.
  8. 설정
    • 9-setting_0
    • 설정 영역의 경우 사이트 좌측 하단에 존재합니다. 잘 보이시나요? 9-setting_1 이렇게 생겼답니다. 이 영역도 어느정도 눈에 띄도록 개편이 필요할 것같습니다.
    • 배경화면, 위치설정, 추천사이트 등에 대해 설정할 수 있습니다. 9-setting_2 9-setting_3 9-setting_4

최근에 사내테스트를 진행 했고, 위의 내용을 조금씩 더 개선중입니다. 더 멋진 모습으로 소개해드릴 날이 올 수 있습니다!


3. 개발 과정 소개

(1) 프로젝트 구조

Zum-Chrome-Extension
├─ dist                  # webpack으로 vue project를 패키징한 결과물이 들어있습니다. 
├─ dist-zip              # 확장프로그램의 버전별 압축파일 모음입니다.
├─ public                # 기본적으로 사용될 html,css,js,img 파일들을 모아놓습니다.
├─ scripts               # node.js 스크립트
└─ src                   # webpack의 entry point 입니다.
   ├─ assets             # 컴포넌트에 필요한 resource를 모아놓은 폴더입니다.
   ├─ components         # 컴포넌트들을 모아놓습니다.
   ├─ constant           # 앱 내에서 사용 되는 상수를 모아놓습니다.
   ├─ filters            # Vue Component에서 사용되는 filters를 정의합니다.
   ├─ services           # 각종 서비스 로직을 모아놓습니다.
   ├─ storage            # Local Storage API, Chrome Storage API 등을 추상화하여 관리합니다.  
   ├─ store              # Vuex로 만든 store를 관리합니다.    
   ├─ stub               # 개발모드에서 사용되는 목업 데이터입니다.
   └─ styles             # 스타일시트를 모아놓는 폴더입니다.

전체적인 프로젝트 구조입니다. 여기에서 핵심이 되는 부분만 간략하게 설명하겠습니다.


Vue App

프로젝트 구조(4)

사내에서 Vue를 사용하고 있기 때문에 Vue-cli를 이용하여 프로젝트를 구성하였습니다.


Manifest.json

프로젝트 구조(2)

크롬 확장프로그램을 만들 때 제일 중요한 것이 바로 manifest.json입니다. manifest.json은 Vue App을 빌드했을 때 root에 위치해야 하기 때문에 public 폴더에 위치시켰습니다.

이에 대한 설명은 뒤에 상세하게 다루도록 하겠습니다.


Build Script

프로젝트 구조(3)

확장프로그램을 스토어에 등록하거나 혹은 개발자모드에서 확인 하기 위해선 일단 압축을 해야합니다.
build-zip.js를 실행하면 manifest.json에 명시된 버전을 파싱하고 dist 폴더를 압축하여
zum-newtab-v{$version}.zip 형식의 이름으로 dist-zip에 만들어줍니다.


여기까지가 크롬 확장프로그램을 만드는데 필요한 최소한의 프로젝트 구성입니다. 이제 본격적으로 어떤식으로 개발했는지 소개하겠습니다.


(2) manifest.json

확장프로그램에서 제일 중요한 게 바로 manifest.json입니다.

manifest.json은 json 포맷 파일로서, 모든 웹 익스텐션이 포함하고 있어야 하는 파일이며 manifest.json에 확장프로그램의 이름, 버젼과 같은 기본 정보를 명시해야 합니다. 뿐만아니라 반드시 확장프로그램이 사용하는 기능을 명시해야합니다.
(ex: background scripts content scripts browser actions)

그리고 실행할 HTML, Javascript 등을 지정할 수 있습니다.

그래서 manifest.json만 보면 확장프로그램이 어떤 일을 하는지 대강 확인해볼 수 있습니다.

{
  "manifest_version": 2, // Manifest 버전 명시. 공식문서 가이드에 따라 `2`로 고정
  "name": "Zum NewTab", // 확장 프로그램 이름
  "description": "New Tab 활용하여 사용자의 웹 서핑 생산성을 높여주는 줌 시작페이지 제공",
  "version": "1.1.7.0", // 확장 프로그램 버전

  "browser_action": {
    "default_icon": "icon.png" // 확장 프로그램의 아이콘
  },

  "permissions": [
    "bookmarks",             // 북마크에 접근할 수 있는 API 사용 권한
    "topSites",              // 자주방문한 사이트 목록을 조회할 수 있는 API 사용 권한
    "https://*.zum.com/*"    // 모든 zum.com host에 접근할 수 있는 권한
    // "<all_urls>"          // 모든 호스트(사이트)에 접근할 수 있는 권한
    // "activeTab",          // 현재 활성중인 탭에 대해 다룰 수 있는 API 사용 권한
    // "tabs",               // 열려 있는 탭에 대해 다룰 수 있는 API 사용 권한
    // "storage",            // 일종의 브라우저 데이터베이스 API 사용 권한
    // "history",            // 방문기록에 접근할 수 있는 API 사용 권한
  ], 

  // 리소스에 대한 보안정책을 설정. 줄여서 CSP라고 불린다.
  "content_security_policy":  "script-src 'self' 'unsafe-eval'; script-src-elem 'self' 'unsafe-eval' https://ssug.api.search.zum.com https://contentsgt.cafe24.com; object-src 'self'; img-src chrome://favicon/ https://*.zumst.com/;",

  "chrome_url_overrides": {
    "newtab": "index.html" // 새 탭을 열었을 때 보여지는 페이지를 설정할 수 있습니다.
  }
}

1) Content Security Policy (CSP)

HTTP Response Header에 Content Security Policy라는 것을 명시하여 보내줄 수 있습니다.

요약하자면 요청한 리소스가 어떤 권한을 사용할 것인지 정확히 명시하는 과정이라고 보면 좋을 것 같습니다.

manifest.json에 명시한 내용을 조금 더 살펴봅시다.

# JavaScript의 유효한 소스(source, src)를 지정합니다.
script-src 'self' 'unsafe-eval';

# <script> 요소의 유효한 소스(source, src)를 지정합니다.
script-src-elem 'self' 'unsafe-eval' https://ssug.api.search.zum.com https://contentsgt.cafe24.com;

# <object>, <embed> 및 <applet> 태그에 대한 유효한 소스(source, src)를 지정합니다.
object-src 'self';

# 이미지 및 파비콘의 유효한 소스(source, src)를 지정합니다.
img-src chrome://favicon/ https://*.zumst.com/;

이렇게 명시했기 때문에 확장프로그램 내에서 외부 리소스를 요청하면 다음과 같이 CSP가 포함된 응답을 건내줍니다.

10-CSP

그리고 이 문서를 살펴보면 <meta> 태그를 이용하여 다음과 같이 명시하는 것도 가능합니다.

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

필자는 확장프로그램을 개발할 때 말곤 아직 CSP를 사용해본적이 없습니다.


2) Chrome API

다음에 소개할 것은 Chrome API입니다.

manifest.json에 다음과 같이 어떤 Chrome API를 사용할지 명시할 수 있습니다.

{
  ...
  "permissions": [
    "bookmarks", // 북마크를 조회할 수 있는 API
    "topSites",  // 자주 방문한 사이트를 조회할 수 있는 API
  ],
  ...
}

위에 명시한 API들은 다음과 같이 사용할 수 있습니다.


chrome.topSites.get(console.log); // 자주 방문한 사이트를 조회할 수 있습니다.
chrome.bookmarks.getTree(console.log); // 북마크를 트리 형태로 조회할 수 있습니다.

11-chrome-api_01 11-chrome-api_02

API 호출 결과는 callback에게 반환하기 때문에 조금 더 유연하게 사용하기 위해선 Promise로 감싸서 사용해야합니다.

const BookmarkService = Object.freeze({

  /** 북마크 트리를 가져옵니다. **/
  getTree () {
    return new Promise(resolve => {
      chrome.bookmarks.getTree(([ tree ]) => resolve(tree));
    })
  },

  /** 북마크의 트리를 목록으로 변환하여 가져옵니다. **/
  async getListAboutTree () {
    const tree = await this.getTree()
    let bookmarks = tree.children.flatMap(v => v.children);
    while (bookmarks.find(v => v.children)) {
      bookmarks = bookmarks.flatMap(v => v.children || [ v ])
    }
    return bookmarks.map(({ id, title, url }) => ({ id, title, url }));
  },
});

// 다음과 같이 사용할 수 있습니다.
BookmarkService.getTree().then(console.log);
BookmarkService.getListAboutTree().then(console.log);

3) 개발모드에 대한 핸들링

13-localserver

확장프로그램을 개발할 땐 로컬서버에서 작업했습니다. 크롬에 확장프로그램 개발모드가 따로 있어서 이를 이용해도 됐으나, 퍼블리싱팀과의 협업을 위해서 비교적 개발환경 자체는 퍼블리싱팀이 최대한 신경쓰지 않도록 작업하는게 필요했습니다.

// BookmarkService.js

export default Object.freeze({
  fetchBookmarks () {
    // 개발 환경에서는 stub data를 반환합니다.
    if (process.env.NODE_ENV === 'development') {
      return resolve(require('../stub/bookmarks'));
    }
    return new Promise(resolve => {    
      chrome.bookmarks.getTree(([ tree ]) => {
          let temp = tree.children.flatMap(v => v.children);
          while (temp.find(v => v.children)) {
            temp = temp.flatMap(v => v.children || [ v ]);
          }
          // BookmarkTreeNode에서 title과 url만 뽑아온다.
          resolve(temp.map(({ title, url }) => ({ title, url: url || '' })))
        }
      )
    });
  }
});

위의 코드는 사용자의 북마크를 가져오는 역할을 수행하고 있습니다. 그런데 개발 모드에서는 Webpack dev-server에서 결과물을 확인하기 때문에 Chrome API를 사용할 수 없습니다. 그래서 현재 환경이 development 일 땐 stub data를 가져오도록 만들었습니다.

if (process.env.NODE_ENV === 'development') {
  return resolve(require('../stub/bookmarks'));
}

여기서 핵심은 다음과 같습니다.

이는 build 시점에 stub data가 bundle에 포함되지 않게 하기 위함입니다. 이처럼 webpack에서 process.envrequire를 이용하여 bundle 시점에 포함되는 데이터의 여부를 간단하게 표현할 수 있습니다.


사이트의 파비콘의 경우 확장프로그램에서는 chrome://favicon/** 형태의 Favicon API를 사용하면 됩니다.

그런데 이건 확장프로그램에서만 호출 가능한 API입니다. 그래서 개발 환경에선 실제 웹 서비스로 제공되는 Favicon API를 사용해야 했습니다.

쭉 찾아본 결과, 구글에서 제공하는 API를 발견할 수 있었습니다.

프로덕션 모드에서 해당 API를 사용해도 무방하지만 도메인이 존재하는 API에 요청을 한다는 것 자체가 검수 과정에서 문제가될 수 있습니다. 그래서 배포할 땐 chrome://favicon을 사용했고, 개발환경에선 https://www.google.com/s2/favicons을 사용했습니다.


사실 이러한 과정이 필요했던 이유는 퍼블리싱 팀과의 협업 때문입니다. 오직 개발에만 집중할 수 있는 환경이 필요하다면 Webpack Chrome Extension Reloader 패키지를 이용하면 좋습니다.

이 패키지는 크롬 확장프로그램을 웹팩 환경에서 개발할 수 있도록 도와줍니다.


4) 확장프로그램을 크롬 개발자 모드에서 확인하기

앞서 언급한 것들은 Webpack Dev-Server에서 작업할 때 필요한 과정이었습니다. 이번에는 확장프로그램을 크롬 개발자 모드에서 확인하는 방법에 대해 소개하겠습니다.

위의 과정을 거쳐서 개발된 확장프로그램의 기능이 정상적으로 작동하는지 확인해볼 수 있습니다.

4. 시스템 아키텍쳐

아키텍쳐는 소개할 수 있는 영역까지만 간단하게 보여드리겠습니다.

(1) Back-End

12-architecture_01

  1. Personal Fortune API
    • Input: 성별, 생년월일
    • Output: 오늘의 운세
  2. Search Suggest API
    • Input: 검색어
    • Output: 추천검색어
  3. Zum APP API
    • 본래 사용 용도는 줌앱에서 필요한 데이터를 가져오기위해 만들어졌습니다.
    • 즉, 외부에서의 접근이 가능한 API입니다.
    • 팀원과 팀장님과 논의한 결과로 확장프로그램에서 필요한 데이터도 줌앱 API에서 만들기로 하였습니다.
    • 줌앱API는 주기적으로 Intenral API를 호출하고 캐싱합니다.
    • 따라서 사용자는 항상 캐싱된 데이터를 이용하게 됩니다.

(2) Front-End

12-architecture_05

Front-End는 위와 같은 모습으로 설계하였습니다. 일반적인 Single Page Application 프로젝트의 구조입니다.

이에대해 조금 더 구체적으로 설명하기 위해선 프로젝트의 package.json이 필요합니다.

{
  "name": "zum-chrome-extension",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "publish": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "build-zip": "node scripts/build-zip.js",
    "build-and-zip": "vue-cli-service build && node scripts/build-zip.js"
  },
  "dependencies": {
    "@babel/plugin-proposal-optional-chaining": "^7.8.3",
    "core-js": "^3.6.5",
    "fetch-jsonp": "^1.1.3",
    "reset-css": "^5.0.1",
    "vue": "^2.6.11",
    "vue-loader": "^15.8.3",
    "vuex": "^3.1.1"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^4.4.1",
    "@vue/cli-plugin-vuex": "^4.4.1",
    "@vue/cli-service": "^4.4.1",
    "archiver": "^4.0.1",
    "node-sass": "^4.14.1",
    "sass-loader": "^8.0.2",
    "vue-template-compiler": "^2.6.11"
  }
}

사실 Vue-cli를 통해서 설치된 것 이외에는 거의 외부 패키지를 설치하지 않았습니다. 여기서 scripts 부분만 조금 더 살펴보겠습니다.

{
  /* ... 생략 ... */
  "scripts": {
    // 개발 서버를 실행합니다.
    "publish": "vue-cli-service serve",

    // webpack으로 src폴더를 패키징하여 public폴더에 합친 후 dist폴더에 저장합니다.
    "build": "vue-cli-service build",

    // dist폴더를 압축하여 dist-zip에 저장합니다.
    "build-zip": "node scripts/build-zip.js",

    // build와 build-zip를 동시에 실행합니다.
    "build-and-zip": "vue-cli-service build && node scripts/build-zip.js"
  },
  /* ... 생략 ... */
}

터미널에서는 다음과 같이 사용할 수 있습니다.

# 개발 서버를 실행합니다. 
> npm run publish
> yarn publish

# webpack으로 src폴더를 패키징하여 public폴더에 합친 후 dist폴더에 저장합니다.
> npm run build
> yarn build

# dist폴더를 압축하여 dist-zip에 저장합니다.
> npm run build-zip
> yarn build-zip

# build와 build-zip를 동시에 실행합니다.
> npm run build-and-zip
> yarn build-and-zip

다른 SPA 프로젝트와 구분되는 부분이 보이지 않나요? 확장프로그램의 경우 앱 소스를 zip 파일로 만들어야 하기 때문에 build만 하는게 아니라 build된 폴더를 zip으로 구성하는 스크립트를 만들어야 했습니다.

코드로 확인해봅시다!

// scripts/build-zip.js
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const DEST_DIR = path.join(__dirname, '../dist');
const DEST_ZIP_DIR = path.join(__dirname, '../dist-zip');

function extractExtensionData () {
  const manifest = require('../public/manifest.json');
  const name = manifest.name.toLowerCase().replace(/\s/g, '-');
  const version = manifest.version.replace(/\./g, '_');
  return { name, version };
};

function buildZip (src, dist, zipFilename) {
  console.info(`Building ${zipFilename}...`);
  const zipFilePath = path.join(dist, zipFilename);

  if (fs.existsSync(zipFilePath)) {
    fs.unlinkSync(zipFilePath);
  }

  const archive = archiver('zip', { zlib: { level: 9 }});
  const stream = fs.createWriteStream(zipFilePath);
  
  return new Promise((resolve, reject) => {
    archive.directory(src, false)
           .on('error', reject)
           .pipe(stream);

    stream.on('close', resolve);
    archive.finalize();
  });
}

const {name, version} = extractExtensionData();
const zipFilename = `${name}-v${version}.zip`;

if(!fs.existsSync(DEST_ZIP_DIR)) fs.mkdirSync(DEST_ZIP_DIR);

buildZip(DEST_DIR, DEST_ZIP_DIR, zipFilename)
  .then(() => console.info('SUCCESS'))
  .catch(console.err);

따라서 build-and-zip을 실행할 경우, build를 하자마자 바로 build-zip 폴더에 압축하여 저장하게 됩니다.


🚩 정리

여기까지 프로젝트 구조를 살펴봤습니다. 다시 정리하자면 다음과 같습니다.


5. 배포 과정 소개

이제 앱스토어에 배포하는 과정에 대해 간단하게 소개하겠습니다.

이렇게 배포하는 과정은 어렵지 않습니다.

아직 최종 관문인 지옥의 검수과정이 남았습니다. 검수 과정 때문에 정말 힘들었습니다..


6. 지옥의 검수 과정

이제 검수과정에 대해 하소연소개합니다.


(1) 첫 번째 게시 요청


(2) 문제점 분석 및 대처

먼저 문제가 될 만한 것들을 생각해봤습니다.

  1. API 호출에 대한 이슈
    • JSONP를 사용해도 되는가
    • 일부 API를 JSONP로 사용하고 있습니다. - 꼭 호출하는 API의 도메인이 개발자의 소유여야만 하는가
    • 그렇다면 Open API는 어떻게 이용해야 하는가?
  2. Manifest.json에서 permission 설정에 대한 이슈
    • 정말로 필요한 권한인가?
  3. Content Security Policy 설정 이슈
  4. Chrome API에 대한 이슈
  5. 컨텐츠 자체의 이슈
    • 사용해도 무방한 컨텐츠인가?
    • 컨텐츠가 포함되어도 상관 없는가?
  6. Zum Front End Core에 대한 이슈
    • 대부분의 Front-End 프로젝트에 Zum Front Core Pacakge를 사용하고 있었습니다.
    • 따라서 확장프로그램에도 자연스럽게 Core를 적용했습니다.
    • 뒤에 서술하겠지만, 결론만 말하자면 결정적으로 Core에 문제가 있었습니다.

이렇게 정리한 다음에 다음과 같은 조치를 취하였습니다.

그 결과..

16-validate_06

전부 반려되었습니다….

emoticon_01

이 결과를 확인했을 때 많이 들어본 말이 떠올랐습니다.

눈을 감아보세요. 아무것도 안 보이죠? 네 그게 당신의 미래입니다.

말 그대로 눈 앞이 깜깜했습니다..


(3) 다시 문제점 분석

팀장님, 팀원들과 머리를 맞대고 다시 고민을 시작했습니다.

16-validate_07


(4) 첫 검수 통과

Core 패키지를 제외한 후에 검수 요청한 것들은 대부분 통과했습니다.

16-validate_08

emoticon_03 emoticon_04

이 때 알 수 있던 사실은 다음과 같습니다.

  1. 컨텐츠 자체에는 문제가 없다.
  2. JSONP를 사용해도 된다.

일단 검수가 성공했기 때문에 여태까지 정리해놓은 이슈들을 하나씩 테스트할 수 있었습니다.


(5) 이어진 분석 - 검수 과정에서 알 수 있었던 것들

  1. API 호출은 소유자가 아니여도 상관 없었습니다.
    • 단, 모든 요청은 https로 보내야합니다. 👈👈👈 매우중요합니다!
  2. 확장프로그램 전용으로 사용할 수 있는 Chrome APIManifest.json에 명시한게 아닐 경우, API가 호출 가능한지만 체크하는 코드 또한 반려사유가 될 수 있습니다.
    • ex) if (chrome.store) { /*...*/ } 이런 코드 또한 반려사유가 됩니다.
  3. 반대로 Manifest.json에 명시했으나 사용하지 않는 기능이 있을 경우에도 반려 사유가 됩니다.

  4. 개인정보 처리방침이 필요합니다.

  5. 리소스를 base64로 사용하면 안 됩니다.
    • webpack을 사용하면 기본적으로 작은 용량의 파일을 base64로 만듭니다.
    • 그래서 vue.config.js 혹은 webpack.config.js에 다음과 같은 코드를 삽입해줘야합니다.
// vue.config.js
module.exports = {
  /* 생략 */
  chainWebpack: config => config.module
                                   .rule('images')
                                   .use('url-loader')
                                   .loader('url-loader')
                                   .tap(options => ({ ...options, limit: -1 }));
  },
  /* 생략 */
};

위의 과정들을 거치면서 이제 정말 검수는 문제 없겠구나 생각했습니다. 실제로 3번 정도는 큰 문제없이 통과했습니다.

그런데 약 한 달을 더 검수에 더 시달려야했습니다… 😂😂

16-validate_09

이렇게 반려된 이유는 검색 모듈 때문이었는데

16-validate_03

사용자에게 무언가를 입력받은 후 특정 페이지에 넘겨줄 경우, 마찬가지로 https로 처리해야 했습니다. 즉, 입력정보를 암호화해야 하는 것입니다.

search.zum.com은 타 팀에서 관리하는 프로젝트이기 때문에 https전환을 요청했고, 약 한 달 정도 기다려야 했습니다. 일단 손놓고 기다리기만 할 순 없는 노릇이고, 또 다른 원인에 대한 가능성도 있기 때문에 다방면의 시도를 해보았습니다.

결과는 앞서 올린 사진처럼 모두 반려되었습니다.

emoticon_01

어쨌든 많은 우여곡절 끝에 마지막 베타버전을 배포할 수 있었습니다.


🚩 정리

앞서 언급한 검수과정의 핵심내용에 대한 요약입니다.

  1. API는 SSL 인증이 된 것(https)만 사용 가능합니다.
    • 가능하면 확장프로그램 내에 연결된 모든 사이트가 SSL인증을 받은 상태면 더 좋습니다.
  2. jsonp를 사용해도 됩니다.
    • 단, Content Security Policy를 명확하게 작성해야 합니다.
  3. 다른 사이트의 파비콘이 필요한 경우 chrome://favicon을 사용하면 됩니다.
  4. manifest.json에 명시한 Chrome API는 무조건 사용해야 합니다.
    • 명시해놓고 사용하지 않으면 반려
    • 명시하지 않았는데 사용해도 반려
    • if 조건으로 언급하는 것도 반려
  5. 대시보드에 개인정보 처리방침 링크를 올려야합니다.
    • 정말 대충 작성해도 상관 없으니까 올려놓기만 하면 됩니다!
    • 예시: https://www.better-image-description.com/chromeprivacy.html
    • 간단한 확장프로그램의 경우는 필요없습니다.
  6. 리소스를 base64로 사용하면 반려됩니다.
  7. 쿠키 사용은 자제할 수록 좋습니다.
    • 사용하더라도 권한에 명시하면 됩니다. (확실하진 않습니다.)
    • 그래도 사용하지 않는게 제일 좋습니다. (안전하게!)
  8. 사용자에게 무언가 입력을 받은 후 다른 사이트에 넘기는 경우(검색, 로그인 등) 무조건 해당 사이트는 무조건 SSL(https) 인증을 받아야합니다.
    • 즉, 사용자 정보(입력정보)에 대한 암호화가 되어있어야 합니다.
    • https로 인증하기 힘들다면 proxy 혹은 redirect를 통해서 우회해도 상관없습니다.
  9. manifest.json에 명시한 권한이 많을 수록 검수가 오래 걸립니다.
  10. 검수는 빠르면 1일, 길면 4일 정도 소요됩니다.
  11. 검수 요청을 올릴 때 manifest.json에 명시한 version은 항상 달라야 합니다.
    • 똑같은 version에 대한 검수가 진행되었을 경우 바로 반려될 수 있습니다.

7. 앞으로의 계획

개인적으로 서비스를 만들 때 마음속, 머릿속에 새겨두는 말이 있습니다.

“서비스는 런칭 이후가 진짜 시작이다.”

서비스를 운영하는 기업의 입장에서 서비스를 만들기까지보단, 만든 후에 운영하는 것부터가 진짜 시작이라고 생각합니다.

그래서 이렇게 확장프로그램을 만들긴 했으나, 아직 시작도 못한 것이라고 생각합니다.

앞으로를 더 기대해주세요!

긴 글 읽어주셔서 정말 감사합니다!


emoticon_05