커스텀 프로토콜로 일렉트론 클라이언트 실행시키기
목적
일렉트론으로 빌드한 A,B 클라이언트가 있다고 하자.
커스텀 프로토콜을 활용하여 A 클라이언트 내에서 로그인 후 특정 버튼을 클릭하면,
B 클라이언트가 실행되면서 A에서 로그인한 정보까지 넘겨주려고 한다.
슬랙 웹페이지에서 슬랙 애플리케이션을 실행시켜 주듯이...
거기다 특정 페이지로 이동까지 시키는 등 다양한 액션이 필요할 수 있다.
우선! windowsOS와 macOS가 각각 작동하는 방식이 다르기 때문에..
여기저기서 쿠키를 등록할 수 있으므로 같은 코드 반복을 방지하기 위해 관련 처리 함수를 만든다.
const setCookie = async (key: string, value: string) => {
session.defaultSession.cookies.set({
domain: ".doodoo.com",
url: process.env.DOODOO_HOST ?? "https://doodoo.com",
name: key,
value,
});
};
const setCookieFromProtocol = async (protocolArgs: URLSearchParams | null) => {
if (protocolArgs && protocolArgs.has("loginInfo")) {
const loginInfo = protocolArgs.get("loginInfo");
if (loginInfo !== null) {
await setCookie("loginInfo", loginInfo);
}
}
};
windowsOS
빌드한 클라이언트를 설치할 때 커스텀 프로토콜을 함께 등록해 주도록,
NSIS 스크립트를 작성하고 config.json에 해당 스크립트 파일을 등록해 준다.
*electron-builder에서 커스텀 NSIS스크립트를 등록하는 방법은 두 가지!
config.json에서 include와 script 옵션을 이용할 수 있다. ( 참고 : NSIS - electron-builder )
installScript.nsh
Var SystemDrive
RequestExecutionLevel admin
!macro customInstall
DeleteRegKey HKCR "doodoo" # 이전에 등록된 경우 프로토콜 제거
SetRegView 64
# URL 프로토콜 핸들러 등록
WriteRegStr HKCR "doodoo" "URL protocol" ""
# 매개변수를 받아 경로에 있는 파일을 실행시킴
WriteRegStr HKCR "doodoo\shell\open\command" "" '"$클라이언트 실행파일 경로" "%1"'
SetRegView 32
WriteRegStr HKCR "doodoo" "URL protocol" ""
WriteRegStr HKCR "doodoo\shell\open\command" "" '"$클라이언트 실행파일 경로" "%1"'
!macroend
!macro customUnInstall
DeleteRegKey HKCR "doodoo"
!macroend
* preInit등 기본적으로 작성되는 부분은 제외하고 프로토콜을 등록하는 부분만 작성하였음 *
config.json
"nsis": {
"oneClick": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"perMachine": true,
"allowElevation": true,
"shortcutName": "두두두둥=3",
"allowToChangeInstallationDirectory": false,
"installerLanguages": ["en_US", "ko_KR"],
"language": "1042",
"include": "./installScript.nsh"
},
process.argv는 Node.js 프로세스에 전달된 commandLine arguments를 포함하는 배열이다.
Electron 또한 Node.js를 기반으로 하기 때문에 process.argv를 사용할 수 있다!
일반적으로 process.argv에는 아래 값들이 포함된다.
1. Node.js 실행 파일의 경로
2. 실행 중인 Javascript 파일의 경로
3. 그 이후의 커맨드라인에서 전달된 추가 인수
예를 들어서 doodoo://userInfo=1234 를 호출하면 process.argv에는 아래와 같은 값이 포함된다.
["$클라이언트 실행파일 경로/DooDooClient.exe",
"/.../app/main.js",
"doodoo://userinfo=1234"]
will-finish-launching 이벤트는 애플리케이션 실행 과정 중 초기 단계에서 발생하기 때문에,
이 이벤트가 발생하는 시점에서 이미 process.argv에 커스텀 프로토콜 URL이 포함돼 있다.
그러니까 파라미터도 함께 읽어올 수 있게 되는 것이다!
app.on("will-finish-launching", async () => {
if (platform === "darwin") {
return;
}
const protocolUrl = process.argv.find((arg) => arg.startsWith("doodoo://"));
if (protocolUrl) {
const query = protocolUrl.substring("doodoo://".length);
const params = new URLSearchParams(query.substring(0, query.length - 1));
protocolArgs = params;
}
if (mainWindow) {
await setCookieFromProtocol(protocolArgs);
}
});
그런데, 이미 다른 방법으로 애플리케이션이 켜져 있는 상태에서 프로토콜을 호출하면 어떻게 될까?
*기본적으로 macOS에서는 동일한 클라이언트를 여러 개 실행시키는 게 제한된다.
윈도우에서도 app.requestSingleInstanceLock()을 통해 설정할 수 있다.
이 때는 새로운 창을 켜는 게 아니기 때문에 will-finish-launching 이벤트가 트리거되지 않는다.
클라이언트가 켜져 있다고 해도 특정 페이지로 이동하는 액션 등을 취하고 싶다면 second-instance 이벤트를 정의해 준다.
app.on("second-instance", async (event, commandLine, workingDirectory) => {
// winOS: 클라이언트 켜져 있을 때 프로토콜 처리
if (platform === "darwin") {
return;
}
const urlStr = commandLine.find((arg) => arg.startsWith("doodoo://"));
if (urlStr) {
const query = urlStr.substring("doodoo://".length);
// 윈도우는 맨 뒤에 / 같이 들어가므로 별도 처리
const params = new URLSearchParams(query.substring(0, query.length - 1));
protocolArgs = params;
if (!params) {
return;
}
if (mainWindow) {
await setCookieFromProtocol(protocolArgs);
}
}
});
will-finish-launching에서는 process.argv에서 URL을 불러 왔다.
이는 second-instance에서 받아올 수 있는 commandLine(argv)와 동일하게 작동하므로 둘 중에 무엇을 사용해도 무관하다.
main.ts
import os from "node:os";
import { BrowserWindow, app } from "electron";
import { setCookieFromProtocol } from "./window.js";
let mainWindow: BrowserWindow | undefined;
let protocolArgs: URLSearchParams | null = null;
const platform = os.platform();
app.whenReady().then(async () => {
if (mainWindow) {
await setCookieFromProtocol(protocolArgs);
}
await mainWindow?.loadURL("doodoo.com");
});
app.on("second-instance", async (event, commandLine, workingDirectory) => {
// winOS: 클라이언트 켜져 있을 때 프로토콜 처리
if (platform === "darwin") {
return;
}
const urlStr = commandLine.find((arg) => arg.startsWith("doodoo://"));
if (urlStr) {
const query = urlStr.substring("doodoo://".length);
// 윈도우는 맨 뒤에 / 같이 들어가므로 별도 처리
const params = new URLSearchParams(query.substring(0, query.length - 1));
protocolArgs = params;
if (!params) {
return;
}
if (mainWindow) {
await setCookieFromProtocol(protocolArgs);
}
}
});
// winOS : 클라이언트 꺼져 있을 때 프로토콜 처리
app.on("will-finish-launching", async () => {
if (platform === "darwin") {
return;
}
const protocolUrl = process.argv.find((arg) => arg.startsWith("doodoo://"));
if (protocolUrl) {
const query = protocolUrl.substring("doodoo://".length);
const params = new URLSearchParams(query.substring(0, query.length - 1));
protocolArgs = params;
}
if (mainWindow) {
await setCookieFromProtocol(protocolArgs);
}
});
macOS
config.json을 이용해 프로토콜을 등록해 준다. (참고 : Common Configuration - electron-builder )
{
"productName": "DooDooClient",
"appId": "doodoo.client",
... 다른 옵션들
"protocols": {
"name": "doodoo",
"schemes": ["doodoo"]
}, ... 그 외 옵션들
}
macOS에서는 윈도우OS와 달리 커스텀 프로토콜 URL이 process.argv에 직접 포함되지 않기 때문에,
open-url 이벤트* 처리를 통해 urlString을 읽어 와서 처리해 주어야 한다.
그리고 URL이 열릴 때마다 호출되기 때문에 윈도우OS처럼 여러 종류의 이벤트를 처리해 주어야 할 필요도 없어 비교적 간단하다.
*open-url 이벤트는 macOS에서만 지원된다.
main.ts
import { BrowserWindow, app } from "electron";
import { setCookieFromProtocol } from "./window.js";
let mainWindow: BrowserWindow | undefined;
let protocolArgs: URLSearchParams | null = null;
app.whenReady().then(async () => {
if (mainWindow) {
await setCookieFromProtocol(protocolArgs);
}
await mainWindow?.loadURL("doodoo.com");
});
// MacOS : 프로토콜 처리
app.on("open-url", async (event, urlStr) => {
event.preventDefault();
const query = urlStr.substring("doodoo://".length);
const params = new URLSearchParams(query);
protocolArgs = params;
if (!params) {
return;
}
protocolArgs = params;
if (mainWindow) {
await setCookieFromProtocol(protocolArgs);
}
});
참고용)대략적인 electron의 이벤트 실행 순서
*일부 이벤트는 조건에 따라 발생 여부와 순서가 달라질 수 있으며,
macOS 전용 이벤트는 다른 OS에서는 발생하지 않는다. 또한 일부 이벤트는 여러 번 발생할 수도 있다.
- will-finish-launching
- ready
- browser-window-created (윈도우가 생성될 때마다 발생)
- web-contents-created (웹 컨텐츠가 생성될 때마다 발생)
- child-process-gone (자식 프로세스가 종료될 때마다 발생)
- open-file (파일을 열 때마다 발생, macOS에서만 사용)
- open-url (URL을 열 때마다 발생, macOS에서만 사용)
- activate (애플리케이션이 활성화될 때 발생, macOS에서만 사용)
- continue-activity (다른 작업에서 애플리케이션으로 전환될 때 발생, macOS에서만 사용)
- browser-window-blur (윈도우가 비활성화될 때마다 발생)
- browser-window-focus (윈도우가 활성화될 때마다 발생)
- certificate-error (인증서 오류가 발생할 때마다 발생)
- select-client-certificate (클라이언트 인증서를 선택해야 할 때마다 발생)
- login (기본 세션 또는 어떤 세션이든 인증이 필요할 때마다 발생)
- gpu-info-update (GPU 정보가 업데이트될 때마다 발생)
- accessibility-support-changed (접근성 지원이 변경될 때마다 발생, macOS/windowsOS에서만 사용)
- session-created (새 세션이 생성될 때마다 발생)
- second-instance (두 번째 인스턴스가 실행될 때 발생)
- window-all-closed (모든 윈도우가 닫힐 때 발생)
- before-quit (애플리케이션이 종료되기 전에 발생)
- will-quit (애플리케이션이 곧 종료될 때 발생)
- quit (애플리케이션이 종료될 때 발생)
'웹 > ETC' 카테고리의 다른 글
알림서비스를 위해 웹소켓 통신을 rabbitMQ로 대체하기 (0) | 2024.04.22 |
---|---|
일렉트론 로그 확인하기 (0) | 2024.04.09 |
한글단어 자동완성 삽질(용두사미 주의) (0) | 2024.01.22 |