크롬 익스텐션 개발기

JavaScript

Summary

라이선스 발급 취소 사유 메일 발송 크롬 익스텐션 개발 후기.

Abstract

사내 라이선스 센터 발급 취소시 발급 취소 사유를 이메일로 발송하는 기능에 대한 요구사항으로 개발을 진행했습니다.

라이선스 센터는 2010년에 개발된 레거시 코드이며 소스 수정에 대한 부담이 있습니다.

라이선스 센터의 코드를 수정하지 않고 크롬 익스텐션을 이용하여 해당 요구사항에 대응할 수 있다고 판단했습니다.

Requirements

라이선스 취소 이후 취소 사유를 이메일로 작성해서 피드백을 수행하고 있는데, 반복적인 안내메시지를 작성해야 합니다. 이메일을 작성하는데 소요되는 시간이 불필요한 업무 비효율성을 초래합니다. 하루에도 수십건의 단순 업무를 자동화 혹은 단순화 하는게 요구사항의 핵심입니다.

Solutions

라이선스 센터에서 하단의 “라이선스 요청취소” 버튼을 누르면 크롬 익스텐션이 동작합니다.

우측 상단의 익스텐션 아이콘을 눌러서 기능을 동작시킬 수도 있습니다.

팝업창에서 사유를 선택하고 취소사유를 입력하고 이메일 발송하기 버튼을 클릭하면 outlook이 실행되면서 메일 발송 대상자, 제목, 내용등이 자동으로 입력되고 발송하기 버튼을 눌러 취소 메일 발송 절차를 마무리 합니다.

받는 사람 : 요청자의 이메일 주소가 자동으로 포함됩니다.

참조 : 세일즈팀 이메일 주소가 자동으로 포함됩니다.

제목 : 라이선스 발급 보류 안내 메일입니다. 메시지와 고객사 이름 정보가 자동으로 포함됩니다.

내용 : 고정 메시지 + 발급 거절 사유 + 발급 취소 메시지 로 구성됩니다.

담당자는 보내기 버튼만 눌러 거절 메시지 발송을 마무리 합니다.

Chrome Extension

크롬 익스텐션은 javascript 로 쉽게 개발이 가능합니다.

Architecture

  • popup.html
    • 크롬 익스텐션의 UI를 담당하는 html
  • popup.js
    • popup.html과 직접 상호작용하고, background 스크립트와 함께 API를 호출
  • contentscript.js
    • 사용자가 방문하는 페이지 영역에서 작동하는 스크립트
    • 페이지의 현재 상태에 대한 정보를 전달한다.
    • 현재 페이지의 DOM을 읽어와서 조작이 가능하다.
  • background.js
    • 브라우저 영역에서 작동하는 스크립트.
    • 플러그인의 이벤트 핸들러
    • 중요한 모든 이벤트 리스너가 여기 저장된다.
    • 이벤트가 트리거 되고, 할당된 로직을 실행할 때까지 inactive 상태로 유지된다.

Project tree

manifest.json

모든 확장 프로그램의 루트 디렉터리에는 확장 프로그램의 구조와 동작에 관한 중요한 정보가 나열된 manifest.json 파일이 있어야 합니다.

Coding

manifest.json

{
  "manifest_version": 3,
  "name": "라이선스 거절 메일 발송",
  "description": "license center extension",
  "version": "1.0",
  "permissions": ["activeTab", "tabs", "storage"],
  "action": {
    "default_icon": "icon.png",
    "default_title": "RejectLicense",
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
}

popup.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <h2><img src="icon.png" alt="logo" width="25" height="25" /> 라이선스 거절 안내</h2>
    <span id="idxinfo"></span>
    <h3>사유 선택 :</h3>
    <label><input type="checkbox" id="option1" value="고객정보 수정 필요" />고객정보 수정 필요</label><br />
    <label><input type="checkbox" id="option2" value="장비 정보 수정 필요" />장비 정보 수정 필요</label><br />
    <label><input type="checkbox" id="option3" value="사용자수 수정 필요" />사용자수 수정 필요</label><br />
    <label><input type="checkbox" id="option4" value="발급구분 수정 필요" />발급구분 수정 필요</label><br />
    <label><input type="checkbox" id="option5" value="(내부) 계약 미확인 보류" /> (내부) 계약 미확인보류</label><br />
    <label><input type="checkbox" id="option6" value="기타" /> 기타</label>
    <h3>발급 취소 사유 :</h3>
    <textarea style="width: 600px" rows="7" id="rejectLicenseContents"></textarea>
    <button id="sendRejectEmail">이메일 발송하기</button>
    <button id="closePop">취소</button>
    <script src="popup.js"></script>
  </body>
</html>

popup.js

var request_index = "";
var to_mail = "";
var to_cc = "sales_support@xxx.com";
var subject = "라이선스 발급 보류 안내 메일입니다.";

window.onload = function () {
  document.body.style.width = "700px";
  document.body.style.height = "700px";
  window.resizeTo(700, 700);

  // 발급취소 버튼을 누른 경우 쿼리스트링으로 정보 수신
  var queryString = window.location.search;

  // queryString을 파싱하여 변수 가져오기
  request_index = getUrlParameter("request_index", queryString);
  to_mail = getUrlParameter("to_mail", queryString);
  document.getElementById("idxinfo").innerHTML = request_index;
};

// 파라미터 정보로 부터 값 수신
function getUrlParameter(name, url) {
  name = name.replace(/[\\[\\]]/g, "\\\\$&");
  var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
  results = regex.exec(url);
  if (!results) return null;
  if (!results[2]) return "";
  return decodeURIComponent(results[2].replace(/\\+/g, " "));
}

// 익스텐션을 직접 실행한 경우 정보 수신
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
  // 활성 탭의 정보
  var activeTab = tabs[0];

  // 활성 탭에 메시지를 전송하여 원하는 요소의 값을 가져옴
  chrome.tabs.sendMessage(activeTab.id, { action: "getValue", elementName: "request_index" }, function (response) {
    if (chrome.runtime.lastError) return;
    if (response && response.value) {
      request_index = response.value;
    } 
  });
  chrome.tabs.sendMessage(activeTab.id, { action: "getValue", elementName: "requestmail" }, function (response) {
    if (chrome.runtime.lastError) return;
    if (response && response.value) {
      to_mail = response.value;
    } 
  });
});

// 메일 발송하기 버튼 클릭시 이벤트 처리
document.getElementById("sendRejectEmail").addEventListener("click", function () {
  var mail_contents = makeBody();

  window.location.href = `mailto:${to_mail}?cc=${to_cc}&subject=${subject}-${company_name}&body=${mail_contents}`;
});

// "취소" 버튼 클릭 시 팝업 닫기
document.getElementById("closePop").addEventListener("click", function () {
  window.close();
});

function makeBody() {
  // Logic to send email using selected options
  const option1 = document.getElementById("option1");
  const option2 = document.getElementById("option2");
  const option3 = document.getElementById("option3");
  const option4 = document.getElementById("option4");
  const option5 = document.getElementById("option5");
  const option6 = document.getElementById("option6");

  var reason = "라이선스 발급 요청이 아래와 같은 사유로 발급 보류되었습니다.%0D%0A라이선스 발급 실패 사유 : %0D%0A";
  if (option1.checked) {
    reason += " - " + option1.value + "%0D%0A";
  }
  if (option2.checked) {
    reason += " - " + option2.value + "%0D%0A";
  }
  if (option3.checked) {
    reason += " - " + option3.value + "%0D%0A";
  }
  if (option4.checked) {
    reason += " - " + option4.value + "%0D%0A";
  }
  if (option5.checked) {
    reason += " - " + option5.value + "%0D%0A";
  }
  if (option6.checked) {
    reason += " - " + option6.value + "%0D%0A";
  }
  var reject_contents = document.getElementById("rejectLicenseContents").value;
  return reason + reject_contents.replaceAll("\\n", "%0D%0A");
}

content.js

var btnCancel = document.getElementsByName("btnCancel")[0]; // 취소 버튼 DOM

if (btnCancel) {
  btnCancel.addEventListener("click", function () {
    // 버튼을 클릭한 경우, 값을 가져와서 팝업 스크립트로 전달
    var request_index = document.getElementsByName("request_index")[0].value;
    var to_mail = document.getElementsByName("requestmail")[0].value;
    chrome.runtime.sendMessage({ action: "buttonClicked", request_index: request_index, to_mail: to_mail });
  });
}

// 컨텐트 스크립트에서 메시지를 수신하고 처리
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  if (request.action === "getValue") {
    // 요소의 이름과 일치하는 모든 요소를 가져옴 (getElementsByName은 배열을 반환)
    var elements = document.getElementsByName(request.elementName);

    // 배열의 첫 번째 요소가 존재하고 value 속성이 있는 경우 해당 값을 응답으로 보냄
    if (elements.length > 0 && elements[0].hasAttribute("value")) {
      sendResponse({ value: elements[0].value });
    } else {
      sendResponse({ value: null });
    }
  } 
});

background.js

// background.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  if (request.action === "buttonClicked") {
    // 추가적인 변수를 전달하기 위한 쿼리 매개변수를 생성
    var queryString = "request_index=" + request.request_index + "&to_mail=" + request.to_mail;
    chrome.windows.create({
      url: "popup.html?" + queryString,
      type: "popup",
      width: 700,
      height: 800,
    });
  }
});

Manage Extensions

확장 프로그램 등록하기

크롬 확장 프로그램의 확장 프로그램 관리 메뉴를 클릭합니다.

왼쪽 상단에 “압축해제된 확장 프로그램을 로드합니다.” 항목을 클릭합니다.

소스코드가 존재하는 디렉토리를 선택하면 아래와 같이 확장 프로그램이 추가됩니다.

확장 프로그램 아이콘을 클릭하여 핀셋 아이콘을 누르면 익스텐션 목록에 아이콘이 추가되어 언제든지 실행 가능한 상태가 됩니다.

아이콘을 클릭해 원하는 테스트를 수행해 봅니다.

확장 프로그램 디버깅

확장 프로그램 관리 화면에서 오류 발생 상황을 확인가능합니다. 오류 버튼을 클릭하면 상세한 오류 메시지를 확인 가능합니다. popup.html 에서 개발자 도구를 띄워 콘솔 로그를 확인하면서 오류를 수정할 수도 있습니다.

확장 프로그램 업데이트

코드 수정이 완료되면 리프레시 버튼을 누르면 즉시 수정된 코드가 반영됩니다.

Limitation

크롬 익스텐션에서 바로 메일을 발송하는 방법은 없습니다. 따라서 mailto 태그를 이용해 메일 발송 기능과 연동하는 수준으로 개발을 진행했습니다. 바로 메일을 보내고 싶다면 API 서버를 만들어 해당 API로 정보를 전달하고 전달받은 서버에서 메일 발송 처리를 수행하도록 구현하면 됩니다.

mailto 태그의 character 수 제한은 2000자 입니다. 따라서 mail body 내용 작성에 제한이 있습니다.

2042 characters on Chrome 64.0.3282.186 · 2046 characters on Edge 16.16299 · approximately 32700 characters on Firefox 58.0.

Conclusion

지금까지 레거시 코드의 수정 없이 크롬 익스텐션을 이용하여 원하는 기능이 수행될 수 있도록 처리해 보았습니다. 생각보다 크롬 익스텐션은 기본적인 자바스크립트에 대한 이해만 있다면 누구든지 쉽게 개발을 진행할 수 있었습니다. 브라우저의 DOM을 접근하여 조작이 가능하고 정보를 얻을 수도 있으며, 스토리지 기능을 이용하여 정보가 휘발되지 않도록 관리도 가능하며, 북마크나 방문 히스토리에도 접근이 가능합니다.

좋은 아이디어만 있다면 유용한 익스텐션을 개발할 수 있을 것이며, 공개 익스텐션을 개발하여 크롬 웹 스토어에 개시도 해볼 수 있을 거라 생각합니다.

References

확장 프로그램 / 시작하기 | Get started | Chrome for Developers

velog

다음 글: GIT 초기 명령어 이전 글: HUGO 사이트 배포를 위한 배치 파일 생성

See Also