[Typescript] Chrome Extension에서 중단 없이 Interval 사용하기 (manifest v3)
서론
본 포스팅에선 필자가 크롬 확장프로그램을 개발하면서 겪었던 Interval 사용 문제에 대해 다뤄볼까 한다.
TL;DR
- Chrome Extension manifest v3 서비스워커의 실행시간은 최대 5분으로 제한되므로 Interval을 사용하기 위해 setInterval을 사용하는 것보단 chrome.alrams의 create()와 onAlram.addListener()의 사용을 권장한다.
- chrome.alrams.create()로 생성된 Alram 객체는 단순히 알람의 이름과 시간간격만을 지정할 수 있기 때문에 setInterval처럼 callback 함수를 파라미터로 넘겨줄 수 없다. 그래서 Alram 별로 ID나 태그 등의 serialize가 가능한 인자를 넘겨주고 싶다면, 알람의 이름을 "{알람타입}_{ID나 태그 등등}" 형식의 문자열로 지정하고 onAlram.addListener()에서 알람의 이름을 받아 구분자로 split하여 ID나 매개변수를 파라미터로 넘겨준 것 처럼 사용할 수 있다.
- 그 외에 alram을 이용하여 Interval을 100% 대체하기 어려운 로직은 chrome.scripting.excuteScript()를 사용하여 타겟 Tab에 setInterval을 주입하는 방법으로 setInterval을 사용할 수 있다. 다만, 이러한 방법은 타겟 Tab에 스크립트를 주입하는 방식이기에 Typescript 상에서 타입체크를 해줄 수 없어서 가급적 alram으로 대체할 수 없는 부분만 사용하길 권장한다.
1. 크롬 확장 v3 서비스워커의 실행시간은 최대 5분으로 제한된다
크롬 확장이 manifest v2에서 v3로 바뀌면서 백그라운드 페이지가 서비스워커로 바뀌게 되었는데, 서비스워커는 안타깝게도 최대 실행시간이 5분으로 제한된다. 크롬 브라우저는 성능 최적화를 위해 작업이 수행되지 않는 서비스워커는 비활성화하여 메모리를 해제하는데, 작업이 계속 돌아가는 서비스워커도 5분이 넘게 지속적으로 작업을 수행하게 되면 크롬 브라우저가 강제적으로 비활성화시켜버린다.
manifest v2는 백그라운드 페이지에서 최대 실행 시간에 대한 제한이 없어서 setInterval을 문제 없이 사용할 수 있다. 그래서 다른 블로그들에선 v3의 실행시간 제한 문제의 해결책으로 v3를 v2로 변경하여 사용하라는 방법을 제시하는 경우도 있지만, 현재는 구글에서 manifest v2 기반의 확장프로그램은 신규 등록을 허가하고 있지 않기 때문에 이러한 방법은 사용이 불가능하다.
그러다보니 서비스워커 내에서 setInterval을 이용해 interval 타이머를 생성하여 일정시간마다 반복작업을 수행할 때 만약 5분이 넘게된다면 크롬브라우저는 강제적으로 서비스워커를 비활성화하고 메모리를 해제하기 때문에 interval 타이머 또한 해제되고, 이러한 특징으로 서비스워커는 이벤트 기반 처리 구조를 가질 필요가 있게 되었다.
2. 해결방법
2-1. chrome.alrams로 setInterval을 대체하자
구글에서는 서비스워커 내에서 setInterval 대신 chrome.alrams의 사용을 권장하고있다. 앞서 말했듯이 서비스워커는 주기적으로 비활성화되기 때문에, 서비스워커 내에선 이벤트 기반 처리 구조가 필요하다. chrome.alrams 내 create(), onAlram.addListener()를 사용하면 Interval을 이벤트 기반으로 구현할 수 있다.
chrome.alarms.create(name: string, alramInfo: chrome.alrams.AlramCreateInfo)로 알람의 이름과 발생시간을 설정해줄 수 있는데 setInterval과 차이점이 있다고 하면, create를 사용해 알람을 생성할때 callback 함수를 같이 전달할 수 없다는 것이다. 그래서 만약 알람별로 ID나 태그 등의 serialize가 가능한 인자를 넘겨주고 싶다면 알람 이름을 지정하는 name에 구분자를 이용하여 파라미터를 넣어주면 된다. 이후 onAlram.addListener()에서 알람의 이름을 받아 구분자로 split한 후 각 파라미터들을 이용하면 setInterval과 비슷한 효과를 낼 수 있다. 필자는 다음과 같은 알람 이름 형식을 권장한다.
chrome.alrams.create("{알람 타입}_{파라미터1}_{파라미터2}...{파라미터n}", {periodInMinutes: 1});
create()를 보면 alramInfo 파라미터가 있는데, alramInfo는 알람의 최초 발생 지연 시간과 반복 발생 시간 간격을 지정할 수 있는 옵션 객체이다. periodInMinutes는 알람의 반복 발생 시간 간격을 의미한다. 단, 분 단위로만 지정이 가능하기 때문에 만약 초 단위로 사용하기 위해선 1을 60으로 나누어 계산하는 과정이 필요하다. delayInMinutes와 when 프로퍼티가 동일하게 최초 알람 발생 지연 시간을 의미하고 있지만, 이 두 프로퍼티를 동시에 사용할 수는 없다. 필요에 따라 분 단위가 필요하면 delayInMinutes를, 밀리초 단위가 필요하다면 when을 사용해야 한다.
declare namespace chrome.alarms {
interface AlramCreateInfo {
periodInMinutes?: number | undefined; // 알람 반복 발생 시간 간격 (분 단위)
delayInMinutes?: number | undefined; // 최초 알람 발생 지연 시간 (분 단위)
when?: number | undefined; // 최초 알람 발생 지연 시간 (밀리초 단위)
}
}
create()를 이용해 알람을 생성하면 이제 onAlram.addListener()를 통해 알람 발생을 수신할 수 있다.
onAlram.addListener()에 인자로 전달되는 핸들러 함수는 발생한 알람 객체를 인자로 전달 받는다. 그래서 전달 받은 알람 객체의 지정된 이름을 확인하고 이를 이용해 알람별로 구분된 작업을 수행할 수 있게 된다. 이때 위에서 소개했던 이름을 통한 인자 전달 방법을 이용했다면, onAlram.addListener() 내에서 이름을 구분자로 split하여 사용하면 된다.
하지만, 아무래도 setInterval과 다르게 직접적으로 callback 함수를 전달할 수 없다보니 serialize가 가능한 타입의 인자만 전달할 수 있어서 기능 구현에 제약이 발생할 수 있다. 그래서 꼭 setInterval를 사용해야하는 경우 다음 방법을 이용할 수 있다.
2-2. chrome.scripting.excuteScript()를 이용하여 타겟 Tab에서 Interval을 돌리자
타겟 Tab에서 setInterval()을 실행하여 interval을 생성하면 서비스워커의 비활성화와 상관없이 평소와 동일하게 setInterval을 사용할 수 있다.
구현법은 간단하다. excuteScript()를 통해 setInterval()을 사용하면 되는데, 이때 setInterval의 리턴 값을 전역 변수로 받아두면 나중에 다른 excuteScript에서 해당 전역 변수를 통해 clearInterval()을 사용할 수 있다.
사실 이 방법은 그렇게 추천하고 싶은 방법은 아니다. 기능적으로는 문제가 없는 방법이긴 하지만,
excuteScript()를 이용해서 스크립트를 타켓 Tab에 주입하여 사용하는 방법이기 때문에 excuteScript()에 전달되는 함수 내에서 window 객체를 통해 사용하는 타겟 Tab의 전역 변수 및 함수들에 대한 타입 체크가 불가능하기 때문이다. 하지만 적절히 유효성 검사만 해준다면 큰 문제 없이 활용할 수 있기 때문에 상황에 맞춰서 사용하길 바란다.