Codelab: Gemini를 사용하여 JavaScript로 Chrome 확장 프로그램 빌드하기

1. 소개

Meet 통화에 참여하지만 가장 먼저 참여하고 싶지 않으신가요? 이러한 경우에 해당한다면 Google Cloud에 적합한 솔루션이 있습니다.

이 Codelab을 따라 첫 번째 참여자가 통화에 참여할 때 알림을 보내는 Chrome 확장 프로그램을 만듭니다.

Chrome 확장 프로그램의 다양한 요소를 알아보고 확장 프로그램의 각 부분을 자세히 살펴봅니다. 콘텐츠 스크립트, 서비스 워커, 메시지 전달과 같은 확장 프로그램 함수에 대해 알아봅니다.

참여자가 Meet 통화에 참여할 때마다 알림을 받으려면 매니페스트 v3 출시를 준수해야 합니다.

2. 시작하기 전에

기본 요건

이 Codelab은 초보자에게 적합하지만 JavaScript에 대한 기본적인 이해가 있으면 경험을 크게 향상할 수 있습니다.

설정/요구사항

  • Chrome 브라우저
  • 로컬 시스템에 IDE/편집기가 설정되어 있어야 합니다.
  • gcloud를 사용하여 Gemini API를 사용 설정하려면 gcloud CLI를 설치합니다.

Gemini API 사용 설정

Note that if you're writing the code in the Cloud Shell editor,
then you will have to download the folder somewhere on your local filesystem to test the extension locally.

3. 산타 추적 시작하기

기본 확장 프로그램 설치

프로젝트의 루트로 사용할 디렉터리를 만들어 보겠습니다.

mkdir gemini-chrome-ext
cd gemini-chrome-ext

Gemini에게 구체적인 질문을 하기 전에 Chrome 확장 프로그램의 일반적인 구조에 대해 몇 가지 질문을 해 보겠습니다.

프롬프트:

What are the important parts to build a chrome extension?

manifest 파일, background script, 사용자 인터페이스에 관한 세부정보를 지정하는 응답을 받습니다. 이러한 특정 파일에 대해 자세히 알아보겠습니다.

프롬프트:

Create a manifest.json file to build a chrome extension.
Make the name of the extension "Meet Joinees Notifier"
and the author "<YOUR_EMAIL>"

저자 필드에 원하는 이름과 이메일을 사용할 수 있습니다.

Gemini는 필요한 매니페스트 파일의 콘텐츠를 반환하지만 action 필드와 같이 필요하지 않은 추가 필드도 가져옵니다. 설명도 필요합니다. 이 문제를 해결해보겠습니다.

프롬프트:

Remove the "action" field and make the description as
"Adds the ability to receive a notification when a participant joins a Google meet".

이 콘텐츠를 프로젝트 루트의 manifest.json 파일에 넣어 보겠습니다.

이 단계에서 매니페스트 파일은 다음과 같이 표시됩니다.

{
    "name": "Meet Joinees Notifier",
    "version": "1.0",
    "manifest_version": 3,
    "description": "Adds the ability to receive a notification when a participant joins a Google Meet",
    "author": "<YOUR_EMAIL>"
}

이 Codelab에서는 매니페스트 파일에 이러한 필드가 있다고 가정하므로 지금은 매니페스트 파일에 생성된 다른 추가 필드를 삭제하세요.

이제 확장 프로그램이 작동하는지 어떻게 테스트할까요? Gemini에게 물어보겠습니다.

프롬프트:

Guide me on the steps needed to test a chrome extension on my local filesystem.

테스트 방법에 관한 단계는 제공해 줍니다. chrome://extensions으로 이동하여 "Extensions Page"로 이동하고 "Developer Mode" 버튼을 사용 설정합니다. 그러면 "Load unpacked" 버튼이 표시됩니다. 이 버튼을 사용하여 확장 프로그램 파일이 포함된 폴더로 로컬에서 이동할 수 있습니다. 이렇게 하면 "Extensions Page"에서 확장 프로그램을 볼 수 있습니다.

3d802a497ce0cfc2.png92db1999a1800ecd.png

좋습니다. 확장 프로그램이 표시되지만 기능을 추가해 보겠습니다.

4. 콘텐츠 스크립트 추가

콘텐츠 스크립트를 사용하여 https://meet.google.com에서만 JavaScript 코드를 실행하려고 합니다. 확장 프로그램에서 이를 달성하는 방법을 Gemini에게 물어보겠습니다.

프롬프트:

How to add a content script in our chrome extension?

또는 다음과 같이 구체적으로 지정할 수도 있습니다.

프롬프트:

How to add a content script to run on meet.google.com subdomain in our chrome extension?

또는 다른 버전:

프롬프트:

Help me add a content script named content.js to run on meet.google.com subdomain
in our chrome extension. The content
script should simply log "Hello Gemini" when we navigate to "meet.google.com".

Gemini는 manifest.json 파일에서 해야 하는 정확한 변경사항과 content.js 파일에 필요한 JavaScript도 알려줍니다.

content_scripts가 추가되면 매니페스트 파일은 다음과 같이 됩니다.

{
    "name": "Meet Joinees Notifier",
    "version": "1.0",
    "manifest_version": 3,
    "description": "Adds the ability to receive a notification when a participant joins a Google Meet",
    "author": "abc@example.com",
    "content_scripts": [
        {
          "matches": ["https://meet.google.com/*"],
          "js": ["content.js"]
        }
    ]
}

이렇게 하면 'https://meet.google.com' 하위 도메인의 페이지로 이동할 때마다 Chrome이 콘텐츠 스크립트 content.js를 삽입합니다. 이 파일을 추가하고 테스트해 보겠습니다.

content.js 파일에 다음 코드를 추가합니다.

console.log("Hello Gemini");

맞습니다. meet.google.com을 방문하면 JavaScript 콘솔(Mac: Cmd + Opt + J / Win/Linux: Ctrl + Shift + J)에 'Hello Gemini'가 표시됩니다.

manifest.json

{

"name": "Meet Joinees Notifier",

"version": "1.0",

"manifest_version": 3,

"description": "Adds the ability to receive a notification when a participant joins a Google Meet",

"author": "luke@cloudadvocacyorg.joonix.net",

"permissions": [

    "tabs",

    "notifications"

],

"content_scripts": [

    {

        "matches": [

            "https://meet.google.com/*"

        ],

        "js": [

            "content.js"

        ]

    }

]

}

content.js

console.log("Hello Gemini!");

6216bab627c31e6c.png

d61631cd9962ffe5.png

좋습니다. 이제 앱에 JavaScript 관련 기능을 추가할 수 있습니다. 잠시 시간을 내어 무엇을 달성하려고 하는지 생각해 보겠습니다.

콘텐츠 스크립트 개선

회의 페이지(회의에 참여할 수 있는 옵션이 있는 곳)에 있을 때 다른 사용자가 회의에 참여하면 알림을 받을 수 있기를 바랍니다. 이를 위해 회의가 비어 있을 때와 누군가 회의에 참여했을 때 화면이 시각적으로 어떻게 변하는지 살펴보겠습니다.

회의에 참여한 사용자가 없는 경우 다음과 같이 표시됩니다.

fe5a0c95b20e7f72.png

회의에 참여자가 있는 경우의 시각적 요소는 다음과 같습니다.

7a5ef60521d961cc.png

바로 눈에 띄는 두 가지 차이점이 있습니다.

  1. 상태 텍스트가 '다른 사용자가 없습니다'에서 '[사용자] 님이 이 통화에 참여하고 있습니다'로 변경됩니다.
  2. 통화에 참여한 사용자의 이미지가 표시됩니다.

회의에 참여한 사람이 있는지 확인하려면 두 가지 변경사항 모두 작동하지만, 후자의 경우 이미 참여한 사용자에 관한 정보를 얻을 수 있으므로 후자를 사용해 보겠습니다.

단축키 ( Mac: Cmd + Opt + C / Win: Ctrl + Shift + C)를 사용하여 Chrome에서 '요소 검사기'를 연 다음 참여한 사용자의 이미지를 클릭합니다.

클래스 문자열이 거의 없는 이미지가 있으며 이미지의 제목 속성에는 회의에 참여한 사용자의 이름이 있습니다. 또한 이 이미지 태그는 U04fid 클래스가 있는 div로 래핑됩니다. 테스트 회의에 몇 명의 참여자를 추가하면 이 상위 div에 여러 이미지(다른 사용자에 해당)가 호스팅되는 것을 확인할 수 있습니다.

따라서 다음과 같은 부분적인 전략을 고려하고 있습니다.

  1. 클래스가 U04fid인 div에 요소가 포함되어 있는지 감지합니다.
  2. 참여자가 있는 경우 회의에 참여자가 있음을 의미합니다.
  3. 사용자에게 알립니다.

Gemini로 돌아가서 이 작업을 단계별로 수행하는 방법을 물어보겠습니다.

프롬프트:

How to detect for element changes in the HTML page?

MutationObserver와 관련된 해결책을 제공하지만 요구사항과 관련된 내용은 없습니다. 프롬프트를 수정해 보겠습니다.

프롬프트:

How can I detect if an element "div.U04fid" has any child images in the HTML page?

이번에는 MutationObserver가 포함되지 않지만 상위 div를 사용하여 참여자를 확인하는 솔루션을 얻습니다.

접수된 내용은 다음과 같습니다.

const divU04fid = document.querySelector('div.U04fid');
const childImages = divU04fid.querySelectorAll('img');
if (childImages.length > 0) {
  // div.U04fid has at least one child image.
}

접근 방식을 더 수정해 보겠습니다.

프롬프트:

Create a method which checks if the element div.U04fid has any child elements,
if it does it should log it on the console. The method should be called checkJoinees
and it should be called as a callback for MutationObserver which is observing the document.body.

다음 코드 스니펫과 함께 반환됩니다.

function checkJoinees(mutations) {
    for (const mutation of mutations) {
      if (mutation.type === 'childList') {
        // A child node was added or removed.
        if (document.querySelector('div.U04fid') && document.querySelector('div.U04fid').childElementCount > 0) {
          // div.U04fid has at least one child element.
          console.log('div.U04fid has at least one child element.');
        }
      }
    }
    return false;
}

const observer = new MutationObserver(checkJoinees);
observer.observe(document.body, {
  childList: true,
  delay: 1000
});

이 코드를 content.js 파일에 복사하고 확장 프로그램을 새로고침한 다음 Meet 페이지를 새로고침합니다.

이제 누군가 회의에 참여하면 콘솔에서 로그 구문을 볼 수 있습니다.

5. 사용자에게 알림 전송

이제 참여자가 회의에 참여한 시점을 감지할 수 있으므로 Chrome 확장 프로그램에 알림 부분을 추가해 보겠습니다. Chrome 확장 프로그램 문서를 탐색하거나 프롬프트를 조정하여 원하는 것을 알 수 있지만 기본적으로 chrome.notifications.create API를 사용해야 하며 이 메서드 호출은 백그라운드 서비스 워커에서 이루어져야 합니다.

프롬프트:

Using the documentation for chrome notifications tell me how to use the chrome.notifications.create method.

다음은 주요 사항을 포함한 세부 단계입니다.

  • 매니페스트 파일에 notifications 권한을 추가합니다.
  • chrome.notifications.create 호출
  • 호출은 백그라운드 스크립트에 있어야 합니다.

manifest version 3에서 Chrome 확장 프로그램에 백그라운드 스크립트를 추가하려면 manifest.json 파일에 background.service_worker 선언이 필요합니다.

따라서 background.js라는 파일을 만들고 manifest.json 파일에 다음을 추가합니다.

"background": {
        "service_worker": "background.js"
},
"permissions": [
        "notifications"
]

위의 추가 사항을 적용하면 매니페스트 파일은 다음과 같이 됩니다.

{
    "name": "Meet Joinees Notifier",
    "version": "1.0",
    "manifest_version": 3,
    "description": "Adds the ability to receive a notification when a participant joins a Google Meet",
    "author": "<YOUR_EMAIL>",
    "content_scripts": [
        {
          "matches": ["https://meet.google.com/*"],
          "js": ["content.js"]
        }
    ],
    "background": {
        "service_worker": "background.js"
    },
    "permissions": [
            "notifications"
    ]
}

프롬프트:

Create a method sendNotification that calls the chrome.notifications.create
method with the message, "A user joined the call" for a chrome extension with manifest v3,
the code is in the background service worker

이 이미지를 폴더의 루트에 저장하고 이름을 success.png로 바꿉니다.

b2c22f064a3f2d9c.png

그런 다음 background.js에 다음 코드 스니펫을 추가합니다.

function sendNotification(notificationId, message) {
    chrome.notifications.create(notificationId, {
      type: "basic",
      title: "A user joined the call",
      message: message,
      iconUrl: "./success.png"
    });
}

sendNotification("notif-id", "test message");

이제 확장 프로그램 페이지에서 확장 프로그램을 다시 로드하면 알림 팝업이 즉시 표시됩니다.

6. Chrome 확장 프로그램에 메시지 전달 추가

이제 마지막 주요 단계는 콘텐츠 스크립트의 참여자 감지와 백그라운드 스크립트의 sendNotification 메서드를 연결하는 것입니다. Chrome 확장 프로그램의 경우 message passing라는 기법을 통해 이를 수행할 수 있습니다.

이를 통해 Chrome 확장 프로그램의 여러 부분(이 경우 콘텐츠 스크립트에서 백그라운드 서비스 워커) 간에 통신할 수 있습니다. Gemini에게 어떻게 하면 되는지 물어보겠습니다.

프롬프트:

How to send a message from the content script to the background script in a chrome extension

Gemini가 chrome.runtime.sendMessagechrome.runtime.onMessage.addListener에 대한 관련 호출과 함께 대답합니다.

기본적으로 sendMessage를 사용하여 누군가 회의 통화에 참여했다는 콘텐츠 스크립트의 메시지를 전송하고 onMessage.addListener를 콘텐츠 스크립트에서 전송한 메시지에 반응하는 이벤트 리스너로 사용합니다. 이 경우 이 이벤트 리스너에서 sendNotification 메서드 호출을 트리거합니다.

알림 메시지와 action 속성을 백그라운드 서비스 워커에 전달합니다. action 속성은 백그라운드 스크립트가 응답하는 내용을 설명합니다.

따라서 content.js 코드는 다음과 같습니다.

function checkJoinees(mutations) {
    for (const mutation of mutations) {
      if (mutation.type === 'childList') {
        // A child node was added or removed.
        if (document.querySelector('div.U04fid') && document.querySelector('div.U04fid').childElementCount > 0) {
          // div.U04fid has at least one child element.
          sendMessage();
        }
      }
    }
    return false;
}

const observer = new MutationObserver(checkJoinees);
observer.observe(document.body, {
  childList: true,
  delay: 1000
});

function sendMessage() {
    chrome.runtime.sendMessage({
        txt: "A user has joined the call!",
        action: "people_joined"
    });
}

다음은 background.js 코드입니다.

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === "people_joined") {
      sendNotification("notif-id", message.txt);
    }
  });
  

function sendNotification(notificationId, message) {
    chrome.notifications.create(notificationId, {
      type: "basic",
      title: "A user joined the call",
      message: message,
      iconUrl: "./success.png"
    });
}

알림 메시지를 맞춤설정하고 고유한 알림 ID를 가져와 보겠습니다. 알림 메시지에는 사용자 이름을 포함할 수 있습니다. 이전 단계에서 살펴본 것처럼 이미지의 title 속성에서 사용자 이름을 확인할 수 있습니다. 따라서 document.querySelector('div.U04fid > img').getAttribute('title').를 사용하여 참여자의 이름을 가져올 수 있습니다.

알림 ID와 관련해서는 콘텐츠 스크립트의 탭 ID를 가져와서 알림 ID로 사용할 수 있습니다. sender.tab.id.를 사용하여 이벤트 리스너 chrome.runtime.onMessage.addListener 내에서 이를 수행할 수 있습니다.

마지막으로 파일은 다음과 같이 표시됩니다.

manifest.json

{
    "name": "Meet Joinees Notifier",
    "version": "1.0",
    "manifest_version": 3,
    "description": "Adds the ability to receive a notification when a participant joins a Google Meet",
    "author": "<YOUR_EMAIL>",
    "content_scripts": [
        {
          "matches": ["https://meet.google.com/*"],
          "js": ["content.js"]
        }
    ],
    "background": {
        "service_worker": "background.js"
    },
    "permissions": [
            "notifications"
    ]
}

content.js

function checkJoinees(mutations) {
    for (const mutation of mutations) {
      if (mutation.type === 'childList') {
        // A child node was added or removed.
        if (document.querySelector('div.U04fid') && document.querySelector('div.U04fid').childElementCount > 0) {
            const name = document.querySelector('div.U04fid > img').getAttribute('title');
            sendMessage(name);
        }
      }
    }
    return false;
}

const observer = new MutationObserver(checkJoinees);
observer.observe(document.body, {
  childList: true,
  delay: 1000
});

function sendMessage(name) {
    const joinee = (name === null ? 'Someone' : name),
        txt = `${joinee} has joined the call!`;

    chrome.runtime.sendMessage({
        txt,
        action: "people_joined",
    });
}

background.js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === "people_joined") {
      sendNotification("" + sender.tab.id, message.txt); // We are casting this to string as notificationId is expected to be a string while sender.tab.id is an integer.
    }
  });
  

function sendNotification(notificationId, message) {
    chrome.notifications.create(notificationId, {
      type: "basic",
      title: "A user joined the call",
      message: message,
      iconUrl: "./success.png"
    });
}

7. 축하합니다

Gemini의 도움을 받아 단시간에 Chrome 확장 프로그램을 빌드할 수 있었습니다. 숙련된 Chrome 확장 프로그램 개발자든 확장 프로그램을 처음 접하는 사람이든 Gemini를 사용하면 원하는 작업을 달성하는 데 도움이 됩니다.

Chrome 확장 프로그램으로 할 수 있는 다양한 작업을 물어보세요. chrome.storage, alarms 등 둘러볼 만한 API가 많이 있습니다. 막히는 부분이 있으면 Gemini 또는 문서를 사용하여 잘못된 부분을 파악하거나 문제를 해결하는 다양한 방법을 모색하세요.

필요한 도움을 받으려면 프롬프트를 수정해야 하는 경우가 많지만, 모든 맥락 여정을 유지하는 하나의 탭에서 수정할 수 있습니다.