@cartanova/qgrid-cli 2.6.5 → 2.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.ko.md +1 -1
  2. package/README.md +1 -1
  3. package/bundle/dist/application/qgrid/expired-token-reminder.js +61 -0
  4. package/bundle/dist/application/qgrid/slack-user-map.js +49 -0
  5. package/bundle/dist/application/qgrid/token-death.js +20 -4
  6. package/bundle/dist/sonamu.config.js +4 -4
  7. package/bundle/src/application/qgrid/expired-token-reminder.test.ts +53 -0
  8. package/bundle/src/application/qgrid/expired-token-reminder.ts +74 -0
  9. package/bundle/src/application/qgrid/slack-user-map.ts +51 -0
  10. package/bundle/src/application/qgrid/token-death.ts +12 -3
  11. package/bundle/src/sonamu.config.ts +5 -5
  12. package/bundle/web-dist/client/assets/index-B4GlN1IX.css +1 -0
  13. package/bundle/web-dist/client/assets/{index-DTy-s79t.js → index-qs8K8bOv.js} +3 -3
  14. package/bundle/web-dist/client/assets/{logs-DYqUSqg-.js → logs-CvmI4KuC.js} +1 -1
  15. package/bundle/web-dist/client/assets/{monit-GnFS0_Xa.js → monit-CUueNzoL.js} +1 -1
  16. package/bundle/web-dist/client/assets/routes-CAfjRsMY.js +5 -0
  17. package/bundle/web-dist/client/assets/{show-DDgukGuY.js → show-C5vKpp5z.js} +1 -1
  18. package/bundle/web-dist/client/assets/tokens-C9KKP2Yq.js +1 -0
  19. package/bundle/web-dist/client/assets/use-oauth-login-flow-B3o4CXDW.js +1 -0
  20. package/bundle/web-dist/client/index.html +2 -2
  21. package/bundle/web-dist/server/assets/{routes-Sv_4FjtH.js → routes-BfoQmYud.js} +52 -3
  22. package/bundle/web-dist/server/assets/{tokens-h-2A3wfD.js → tokens-Dkd_uXEk.js} +1 -151
  23. package/bundle/web-dist/server/assets/use-oauth-login-flow-YM1nsd-H.js +161 -0
  24. package/bundle/web-dist/server/entry-server.generated.js +3 -3
  25. package/package.json +1 -1
  26. package/skills/qgrid/references/token-auth-quota-lifecycle.md +2 -1
  27. package/bundle/dist/application/qgrid/expired-token-digest.js +0 -78
  28. package/bundle/src/application/qgrid/expired-token-digest.test.ts +0 -49
  29. package/bundle/src/application/qgrid/expired-token-digest.ts +0 -93
  30. package/bundle/web-dist/client/assets/index-BGQSGMcK.css +0 -1
  31. package/bundle/web-dist/client/assets/routes-CBwpn4d-.js +0 -5
  32. package/bundle/web-dist/client/assets/tokens-Co1wPrXX.js +0 -1
  33. package/bundle/web-dist/client/assets/use-update-token-mutation-DqEpK7It.js +0 -1
  34. package/bundle/web-dist/server/assets/use-update-token-mutation-DHCaa7Ku.js +0 -7
package/README.ko.md CHANGED
@@ -92,7 +92,7 @@ CLI는 실행 시 npm의 최신 버전을 확인하고 patch를 포함해 설치
92
92
  | `QGRID_DB_NAME` | `qgrid` |
93
93
  | `SLACK_BOT_TOKEN` | — (미설정 시 토큰 만료 알림 비활성) |
94
94
  | `SLACK_CHANNEL_ID` | — |
95
- | `SLACK_EXPIRY_DIGEST_INTERVAL_MINUTES` | — (미설정 시 비활성 토큰 재알림 끔) |
95
+ | `SLACK_EXPIRY_REMINDER_INTERVAL_MINUTES` | — (미설정 시 만료 알림 반복 끔) |
96
96
  | `SLACK_USER_MAP` | — (`토큰명:SlackUserId` 목록) |
97
97
 
98
98
  서버 동작 관련 환경변수:
package/README.md CHANGED
@@ -93,7 +93,7 @@ Without the `--db` flag, DB connection info is read from:
93
93
  | `QGRID_DB_NAME` | `qgrid` |
94
94
  | `SLACK_BOT_TOKEN` | — (unset disables token-expiry notifications) |
95
95
  | `SLACK_CHANNEL_ID` | — |
96
- | `SLACK_EXPIRY_DIGEST_INTERVAL_MINUTES` | — (unset disables inactive-token reminders) |
96
+ | `SLACK_EXPIRY_REMINDER_INTERVAL_MINUTES` | — (unset disables session-expiry repeats) |
97
97
  | `SLACK_USER_MAP` | — (`tokenName:SlackUserId` pairs) |
98
98
 
99
99
  Server behavior variables:
@@ -0,0 +1,61 @@
1
+ import { SLACK_COLOR, notifySlack } from "../../utils/slack-notify.js";
2
+ import { TokenModel } from "../token/token.model.js";
3
+ import { getSlackUserMap, mentionFor } from "./slack-user-map.js";
4
+ import { getLogger } from "@logtape/logtape";
5
+ //#region src/application/qgrid/expired-token-reminder.ts
6
+ /**
7
+ * 세션 만료 알림 반복.
8
+ *
9
+ * 만료 순간의 알림(`token-death.ts`)은 그때 채널을 보지 않으면 놓친다. 재로그인은 사람이
10
+ * 해야 하는 일이라 놓치면 토큰이 계속 빠진 채로 남는다. 그래서 아직 재로그인되지 않은
11
+ * 토큰을 같은 문구로 다시 알린다 — 사건이 하나이므로 알림도 한 종류다.
12
+ *
13
+ * 여러 토큰이 만료돼 있으면 한 메시지에 모은다. 토큰마다 메시지를 보내면 채널이 시끄러워
14
+ * 정작 읽히지 않는다.
15
+ *
16
+ * 비활성 토큰이 없으면 보내지 않는다 — "이상 없음"을 주기적으로 알리면 채널이 무뎌진다.
17
+ */
18
+ const logger = getLogger(["qgrid", "expired-reminder"]);
19
+ const MINUTE_MS = 6e4;
20
+ function buildReminderContext(tokens, userMap) {
21
+ return tokens.map((token) => {
22
+ const mention = mentionFor(token.name, userMap);
23
+ const label = token.name ?? "unnamed";
24
+ return mention ? `${mention} ${label}` : label;
25
+ }).join("\n");
26
+ }
27
+ async function sendReminder(userMap) {
28
+ const inactive = await TokenModel.findInactive("A");
29
+ if (inactive.length === 0) return;
30
+ await notifySlack({
31
+ title: "세션 만료",
32
+ subject: inactive.length > 1 ? `${inactive.length}건` : inactive[0].name ?? "unnamed",
33
+ context: `${buildReminderContext(inactive, userMap)}\n재로그인이 필요합니다`,
34
+ color: SLACK_COLOR.bad
35
+ });
36
+ }
37
+ let timer = null;
38
+ /**
39
+ * `SLACK_EXPIRY_REMINDER_INTERVAL_MINUTES` 가 양수일 때만 켜진다. 미설정·0 이면 비활성.
40
+ *
41
+ * 인스턴스가 여러 개면 각자 보낸다. 만료 순간 알림과 달리 상태 변화가 없어 DB 로 억제할 수
42
+ * 없고, 하루 몇 건 수준이라 중복을 감수하는 편이 잠금 장치를 두는 것보다 낫다.
43
+ */
44
+ function startExpiredTokenReminder() {
45
+ const minutes = Number(process.env.SLACK_EXPIRY_REMINDER_INTERVAL_MINUTES ?? 0);
46
+ if (!Number.isFinite(minutes) || minutes <= 0) return;
47
+ const userMap = getSlackUserMap();
48
+ logger.info(`expired token reminder every ${minutes}m (${userMap.size} user mappings)`);
49
+ timer = setInterval(() => {
50
+ sendReminder(userMap).catch((e) => logger.warn(`reminder failed: ${e.message}`));
51
+ }, minutes * MINUTE_MS);
52
+ }
53
+ function stopExpiredTokenReminder() {
54
+ if (!timer) return;
55
+ clearInterval(timer);
56
+ timer = null;
57
+ }
58
+ //#endregion
59
+ export { buildReminderContext, startExpiredTokenReminder, stopExpiredTokenReminder };
60
+
61
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhwaXJlZC10b2tlbi1yZW1pbmRlci5qcyIsIm5hbWVzIjpbXSwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYXBwbGljYXRpb24vcWdyaWQvZXhwaXJlZC10b2tlbi1yZW1pbmRlci50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIOyEuOyFmCDrp4zro4wg7JWM66a8IOuwmOuztS5cbiAqXG4gKiDrp4zro4wg7Iic6rCE7J2YIOyVjOumvChgdG9rZW4tZGVhdGgudHNgKeydgCDqt7jrlYwg7LGE64SQ7J2EIOuztOyngCDslYrsnLzrqbQg64aT7Lmc64ukLiDsnqzroZzqt7jsnbjsnYAg7IKs656M7J20XG4gKiDtlbTslbwg7ZWY64qUIOydvOydtOudvCDrhpPsuZjrqbQg7Yag7YGw7J20IOqzhOyGjSDruaDsp4Qg7LGE66GcIOuCqOuKlOuLpC4g6re4656Y7IScIOyVhOyngSDsnqzroZzqt7jsnbjrkJjsp4Ag7JWK7J2AXG4gKiDthqDtgbDsnYQg6rCZ7J2AIOusuOq1rOuhnCDri6Tsi5wg7JWM66aw64ukIOKAlCDsgqzqsbTsnbQg7ZWY64KY7J2066+A66GcIOyVjOumvOuPhCDtlZwg7KKF66WY64ukLlxuICpcbiAqIOyXrOufrCDthqDtgbDsnbQg66eM66OM64+8IOyeiOycvOuptCDtlZwg66mU7Iuc7KeA7JeQIOuqqOydgOuLpC4g7Yag7YGw66eI64ukIOuplOyLnOyngOulvCDrs7TrgrTrqbQg7LGE64SQ7J20IOyLnOuBhOufrOybjFxuICog7KCV7J6RIOydve2eiOyngCDslYrripTri6QuXG4gKlxuICog67mE7Zmc7ISxIO2GoO2BsOydtCDsl4bsnLzrqbQg67O064K07KeAIOyViuuKlOuLpCDigJQgXCLsnbTsg4Eg7JeG7J2MXCLsnYQg7KO86riw7KCB7Jy866GcIOyVjOumrOuptCDssYTrhJDsnbQg66y0646M7KeE64ukLlxuICovXG5pbXBvcnQgeyBnZXRMb2dnZXIgfSBmcm9tIFwiQGxvZ3RhcGUvbG9ndGFwZVwiO1xuXG5pbXBvcnQgeyBub3RpZnlTbGFjaywgU0xBQ0tfQ09MT1IgfSBmcm9tIFwiLi4vLi4vdXRpbHMvc2xhY2stbm90aWZ5XCI7XG5pbXBvcnQgeyBUb2tlbk1vZGVsIH0gZnJvbSBcIi4uL3Rva2VuL3Rva2VuLm1vZGVsXCI7XG5pbXBvcnQgeyBnZXRTbGFja1VzZXJNYXAsIG1lbnRpb25Gb3IgfSBmcm9tIFwiLi9zbGFjay11c2VyLW1hcFwiO1xuXG5jb25zdCBsb2dnZXIgPSBnZXRMb2dnZXIoW1wicWdyaWRcIiwgXCJleHBpcmVkLXJlbWluZGVyXCJdKTtcblxuY29uc3QgTUlOVVRFX01TID0gNjBfMDAwO1xuXG5leHBvcnQgZnVuY3Rpb24gYnVpbGRSZW1pbmRlckNvbnRleHQoXG4gIHRva2VuczogeyBuYW1lOiBzdHJpbmcgfCBudWxsOyBwcm92aWRlcjogc3RyaW5nIH1bXSxcbiAgdXNlck1hcDogTWFwPHN0cmluZywgc3RyaW5nPixcbik6IHN0cmluZyB7XG4gIHJldHVybiB0b2tlbnNcbiAgICAubWFwKCh0b2tlbikgPT4ge1xuICAgICAgY29uc3QgbWVudGlvbiA9IG1lbnRpb25Gb3IodG9rZW4ubmFtZSwgdXNlck1hcCk7XG4gICAgICBjb25zdCBsYWJlbCA9IHRva2VuLm5hbWUgPz8gXCJ1bm5hbWVkXCI7XG4gICAgICAvLyDrqZjshZjsnbQg7J6I7Ja064+EIOyWtOuWpCDthqDtgbDsnbjsp4DripQg64Ko6ri064ukIOKAlCDtlZwg7IKs656M7J20IHByb3ZpZGVyIOuzhOuhnCDsl6zrn6wg6rCc66W8IOqwgOynhOuLpC5cbiAgICAgIHJldHVybiBtZW50aW9uID8gYCR7bWVudGlvbn0gJHtsYWJlbH1gIDogbGFiZWw7XG4gICAgfSlcbiAgICAuam9pbihcIlxcblwiKTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc2VuZFJlbWluZGVyKHVzZXJNYXA6IE1hcDxzdHJpbmcsIHN0cmluZz4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgY29uc3QgaW5hY3RpdmUgPSBhd2FpdCBUb2tlbk1vZGVsLmZpbmRJbmFjdGl2ZShcIkFcIik7XG4gIGlmIChpbmFjdGl2ZS5sZW5ndGggPT09IDApIHJldHVybjtcblxuICBhd2FpdCBub3RpZnlTbGFjayh7XG4gICAgLy8g66eM66OMIOyInOqwhCDslYzrprzqs7wg6rCZ7J2AIOygnOuqqeydhCDsk7Tri6QuIOqwmeydgCDsgqzqsbTsnbTrr4DroZwg64uk66W4IOydtOumhOydhCDrtpnsnbTrqbQg67OE6rCcIOusuOygnOuhnCDsnb3tnozri6QuXG4gICAgdGl0bGU6IFwi7IS47IWYIOunjOujjFwiLFxuICAgIHN1YmplY3Q6IGluYWN0aXZlLmxlbmd0aCA+IDEgPyBgJHtpbmFjdGl2ZS5sZW5ndGh96rG0YCA6IChpbmFjdGl2ZVswXSEubmFtZSA/PyBcInVubmFtZWRcIiksXG4gICAgY29udGV4dDogYCR7YnVpbGRSZW1pbmRlckNvbnRleHQoaW5hY3RpdmUsIHVzZXJNYXApfVxcbuyerOuhnOq3uOyduOydtCDtlYTsmpTtlanri4jri6RgLFxuICAgIGNvbG9yOiBTTEFDS19DT0xPUi5iYWQsXG4gIH0pO1xufVxuXG5sZXQgdGltZXI6IFJldHVyblR5cGU8dHlwZW9mIHNldEludGVydmFsPiB8IG51bGwgPSBudWxsO1xuXG4vKipcbiAqIGBTTEFDS19FWFBJUllfUkVNSU5ERVJfSU5URVJWQUxfTUlOVVRFU2Ag6rCAIOyWkeyImOydvCDrlYzrp4wg7Lyc7KeE64ukLiDrr7jshKTsoJXCtzAg7J2066m0IOu5hO2ZnOyEsS5cbiAqXG4gKiDsnbjsiqTthLTsiqTqsIAg7Jes65+sIOqwnOuptCDqsIHsnpAg67O064K464ukLiDrp4zro4wg7Iic6rCEIOyVjOumvOqzvCDri6zrpqwg7IOB7YOcIOuzgO2ZlOqwgCDsl4bslrQgREIg66GcIOyWteygnO2VoCDsiJhcbiAqIOyXhuqzoCwg7ZWY66OoIOuqhyDqsbQg7IiY7KSA7J206528IOykkeuzteydhCDqsJDsiJjtlZjripQg7Y647J20IOyeoOq4iCDsnqXsuZjrpbwg65GQ64qUIOqyg+uztOuLpCDrgqvri6QuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzdGFydEV4cGlyZWRUb2tlblJlbWluZGVyKCk6IHZvaWQge1xuICBjb25zdCBtaW51dGVzID0gTnVtYmVyKHByb2Nlc3MuZW52LlNMQUNLX0VYUElSWV9SRU1JTkRFUl9JTlRFUlZBTF9NSU5VVEVTID8/IDApO1xuICBpZiAoIU51bWJlci5pc0Zpbml0ZShtaW51dGVzKSB8fCBtaW51dGVzIDw9IDApIHJldHVybjtcblxuICBjb25zdCB1c2VyTWFwID0gZ2V0U2xhY2tVc2VyTWFwKCk7XG4gIGxvZ2dlci5pbmZvKGBleHBpcmVkIHRva2VuIHJlbWluZGVyIGV2ZXJ5ICR7bWludXRlc31tICgke3VzZXJNYXAuc2l6ZX0gdXNlciBtYXBwaW5ncylgKTtcblxuICB0aW1lciA9IHNldEludGVydmFsKCgpID0+IHtcbiAgICBzZW5kUmVtaW5kZXIodXNlck1hcCkuY2F0Y2goKGUpID0+IGxvZ2dlci53YXJuKGByZW1pbmRlciBmYWlsZWQ6ICR7KGUgYXMgRXJyb3IpLm1lc3NhZ2V9YCkpO1xuICB9LCBtaW51dGVzICogTUlOVVRFX01TKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHN0b3BFeHBpcmVkVG9rZW5SZW1pbmRlcigpOiB2b2lkIHtcbiAgaWYgKCF0aW1lcikgcmV0dXJuO1xuICBjbGVhckludGVydmFsKHRpbWVyKTtcbiAgdGltZXIgPSBudWxsO1xufVxuIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7OztBQWtCQSxNQUFNLFNBQVMsVUFBVSxDQUFDLFNBQVMsa0JBQWtCLENBQUM7QUFFdEQsTUFBTSxZQUFZO0FBRWxCLFNBQWdCLHFCQUNkLFFBQ0EsU0FDUTtDQUNSLE9BQU8sT0FDSixLQUFLLFVBQVU7RUFDZCxNQUFNLFVBQVUsV0FBVyxNQUFNLE1BQU0sT0FBTztFQUM5QyxNQUFNLFFBQVEsTUFBTSxRQUFRO0VBRTVCLE9BQU8sVUFBVSxHQUFHLFFBQVEsR0FBRyxVQUFVO0NBQzNDLENBQUMsQ0FBQyxDQUNELEtBQUssSUFBSTtBQUNkO0FBRUEsZUFBZSxhQUFhLFNBQTZDO0NBQ3ZFLE1BQU0sV0FBVyxNQUFNLFdBQVcsYUFBYSxHQUFHO0NBQ2xELElBQUksU0FBUyxXQUFXLEdBQUc7Q0FFM0IsTUFBTSxZQUFZO0VBRWhCLE9BQU87RUFDUCxTQUFTLFNBQVMsU0FBUyxJQUFJLEdBQUcsU0FBUyxPQUFPLEtBQU0sU0FBUyxFQUFFLENBQUUsUUFBUTtFQUM3RSxTQUFTLEdBQUcscUJBQXFCLFVBQVUsT0FBTyxFQUFFO0VBQ3BELE9BQU8sWUFBWTtDQUNyQixDQUFDO0FBQ0g7QUFFQSxJQUFJLFFBQStDOzs7Ozs7O0FBUW5ELFNBQWdCLDRCQUFrQztDQUNoRCxNQUFNLFVBQVUsT0FBTyxRQUFRLElBQUksMENBQTBDLENBQUM7Q0FDOUUsSUFBSSxDQUFDLE9BQU8sU0FBUyxPQUFPLEtBQUssV0FBVyxHQUFHO0NBRS9DLE1BQU0sVUFBVSxnQkFBZ0I7Q0FDaEMsT0FBTyxLQUFLLGdDQUFnQyxRQUFRLEtBQUssUUFBUSxLQUFLLGdCQUFnQjtDQUV0RixRQUFRLGtCQUFrQjtFQUN4QixhQUFhLE9BQU8sQ0FBQyxDQUFDLE9BQU8sTUFBTSxPQUFPLEtBQUssb0JBQXFCLEVBQVksU0FBUyxDQUFDO0NBQzVGLEdBQUcsVUFBVSxTQUFTO0FBQ3hCO0FBRUEsU0FBZ0IsMkJBQWlDO0NBQy9DLElBQUksQ0FBQyxPQUFPO0NBQ1osY0FBYyxLQUFLO0NBQ25CLFFBQVE7QUFDViJ9
@@ -0,0 +1,49 @@
1
+ //#region src/application/qgrid/slack-user-map.ts
2
+ /**
3
+ * 토큰 소유자 → Slack 사용자 멘션.
4
+ *
5
+ * Slack 표시 이름과 토큰명이 자주 어긋나(haze→haze.lee, byeongjun→potados) `users.list`
6
+ * 자동 매칭은 조용히 실패한다. 멘션이 빠지면 알림이 개인에게 닿지 않아 목적 자체가
7
+ * 사라지므로 `SLACK_USER_MAP` 명시 매핑만 쓴다.
8
+ *
9
+ * 공용 계정(bysuco, dev-common 등)은 매핑에 넣지 않는다 — 멘션 없이 이름만 나가는 것이
10
+ * 맞는 동작이다.
11
+ */
12
+ /** `토큰명:SlackUserId` 목록을 파싱한다. */
13
+ function parseSlackUserMap(raw) {
14
+ const map = /* @__PURE__ */ new Map();
15
+ if (!raw) return map;
16
+ for (const entry of raw.split(",")) {
17
+ const [name, userId] = entry.split(":").map((s) => s.trim());
18
+ if (name && userId) map.set(name, userId);
19
+ }
20
+ return map;
21
+ }
22
+ /** `anthropic/yds` → `yds`. provider prefix 없이 저장된 이름도 그대로 받는다. */
23
+ function ownerOf(tokenName) {
24
+ if (!tokenName) return "";
25
+ const slash = tokenName.indexOf("/");
26
+ return slash === -1 ? tokenName : tokenName.slice(slash + 1);
27
+ }
28
+ /** 매핑이 있으면 `<@U...>`, 없으면 빈 문자열. 호출부가 멘션 유무로 문구를 고른다. */
29
+ function mentionFor(tokenName, userMap) {
30
+ const userId = userMap.get(ownerOf(tokenName));
31
+ return userId ? `<@${userId}>` : "";
32
+ }
33
+ /**
34
+ * 환경변수는 프로세스 수명 동안 고정이라 매번 파싱할 이유가 없다. 사망 알림은 요청 경로에서
35
+ * 호출되므로 캐시해 둔다.
36
+ */
37
+ let cached = null;
38
+ function getSlackUserMap() {
39
+ cached ??= parseSlackUserMap(process.env.SLACK_USER_MAP);
40
+ return cached;
41
+ }
42
+ /** 테스트에서 env 를 바꿔 끼울 때 쓴다. */
43
+ function resetSlackUserMapCache() {
44
+ cached = null;
45
+ }
46
+ //#endregion
47
+ export { getSlackUserMap, mentionFor, ownerOf, parseSlackUserMap, resetSlackUserMapCache };
48
+
49
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhY2stdXNlci1tYXAuanMiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2FwcGxpY2F0aW9uL3FncmlkL3NsYWNrLXVzZXItbWFwLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICog7Yag7YGwIOyGjOycoOyekCDihpIgU2xhY2sg7IKs7Jqp7J6QIOupmOyFmC5cbiAqXG4gKiBTbGFjayDtkZzsi5wg7J2066aE6rO8IO2GoO2BsOuqheydtCDsnpDso7wg7Ja06riL64KYKGhhemXihpJoYXplLmxlZSwgYnllb25nanVu4oaScG90YWRvcykgYHVzZXJzLmxpc3RgXG4gKiDsnpDrj5kg66ek7Lmt7J2AIOyhsOyaqe2eiCDsi6TtjKjtlZzri6QuIOupmOyFmOydtCDruaDsp4DrqbQg7JWM66a87J20IOqwnOyduOyXkOqyjCDri7/sp4Ag7JWK7JWEIOuqqeyggSDsnpDssrTqsIBcbiAqIOyCrOudvOyngOuvgOuhnCBgU0xBQ0tfVVNFUl9NQVBgIOuqheyLnCDrp6TtlZHrp4wg7JO064ukLlxuICpcbiAqIOqzteyaqSDqs4TsoJUoYnlzdWNvLCBkZXYtY29tbW9uIOuTsSnsnYAg66ek7ZWR7JeQIOuEo+yngCDslYrripTri6Qg4oCUIOupmOyFmCDsl4bsnbQg7J2066aE66eMIOuCmOqwgOuKlCDqsoPsnbRcbiAqIOunnuuKlCDrj5nsnpHsnbTri6QuXG4gKi9cblxuLyoqIGDthqDtgbDrqoU6U2xhY2tVc2VySWRgIOuqqeuhneydhCDtjIzsi7HtlZzri6QuICovXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VTbGFja1VzZXJNYXAocmF3OiBzdHJpbmcgfCB1bmRlZmluZWQpOiBNYXA8c3RyaW5nLCBzdHJpbmc+IHtcbiAgY29uc3QgbWFwID0gbmV3IE1hcDxzdHJpbmcsIHN0cmluZz4oKTtcbiAgaWYgKCFyYXcpIHJldHVybiBtYXA7XG5cbiAgZm9yIChjb25zdCBlbnRyeSBvZiByYXcuc3BsaXQoXCIsXCIpKSB7XG4gICAgY29uc3QgW25hbWUsIHVzZXJJZF0gPSBlbnRyeS5zcGxpdChcIjpcIikubWFwKChzKSA9PiBzLnRyaW0oKSk7XG4gICAgaWYgKG5hbWUgJiYgdXNlcklkKSBtYXAuc2V0KG5hbWUsIHVzZXJJZCk7XG4gIH1cbiAgcmV0dXJuIG1hcDtcbn1cblxuLyoqIGBhbnRocm9waWMveWRzYCDihpIgYHlkc2AuIHByb3ZpZGVyIHByZWZpeCDsl4bsnbQg7KCA7J6l65CcIOydtOumhOuPhCDqt7jrjIDroZwg67Cb64qU64ukLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG93bmVyT2YodG9rZW5OYW1lOiBzdHJpbmcgfCBudWxsKTogc3RyaW5nIHtcbiAgaWYgKCF0b2tlbk5hbWUpIHJldHVybiBcIlwiO1xuICBjb25zdCBzbGFzaCA9IHRva2VuTmFtZS5pbmRleE9mKFwiL1wiKTtcbiAgcmV0dXJuIHNsYXNoID09PSAtMSA/IHRva2VuTmFtZSA6IHRva2VuTmFtZS5zbGljZShzbGFzaCArIDEpO1xufVxuXG4vKiog66ek7ZWR7J20IOyeiOycvOuptCBgPEBVLi4uPmAsIOyXhuycvOuptCDruYgg66y47J6Q7Je0LiDtmLjstpzrtoDqsIAg66mY7IWYIOycoOustOuhnCDrrLjqtazrpbwg6rOg66W464ukLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG1lbnRpb25Gb3IodG9rZW5OYW1lOiBzdHJpbmcgfCBudWxsLCB1c2VyTWFwOiBNYXA8c3RyaW5nLCBzdHJpbmc+KTogc3RyaW5nIHtcbiAgY29uc3QgdXNlcklkID0gdXNlck1hcC5nZXQob3duZXJPZih0b2tlbk5hbWUpKTtcbiAgcmV0dXJuIHVzZXJJZCA/IGA8QCR7dXNlcklkfT5gIDogXCJcIjtcbn1cblxuLyoqXG4gKiDtmZjqsr3rs4DsiJjripQg7ZSE66Gc7IS47IqkIOyImOuqhSDrj5nslYgg6rOg7KCV7J206528IOunpOuyiCDtjIzsi7HtlaAg7J207Jyg6rCAIOyXhuuLpC4g7IKs66edIOyVjOumvOydgCDsmpTssq0g6rK966Gc7JeQ7IScXG4gKiDtmLjstpzrkJjrr4DroZwg7LqQ7Iuc7ZW0IOuRlOuLpC5cbiAqL1xubGV0IGNhY2hlZDogTWFwPHN0cmluZywgc3RyaW5nPiB8IG51bGwgPSBudWxsO1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0U2xhY2tVc2VyTWFwKCk6IE1hcDxzdHJpbmcsIHN0cmluZz4ge1xuICBjYWNoZWQgPz89IHBhcnNlU2xhY2tVc2VyTWFwKHByb2Nlc3MuZW52LlNMQUNLX1VTRVJfTUFQKTtcbiAgcmV0dXJuIGNhY2hlZDtcbn1cblxuLyoqIO2FjOyKpO2KuOyXkOyEnCBlbnYg66W8IOuwlOq/lCDrgbzsmrgg65WMIOyTtOuLpC4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNldFNsYWNrVXNlck1hcENhY2hlKCk6IHZvaWQge1xuICBjYWNoZWQgPSBudWxsO1xufVxuIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFZQSxTQUFnQixrQkFBa0IsS0FBOEM7Q0FDOUUsTUFBTSxzQkFBTSxJQUFJLElBQW9CO0NBQ3BDLElBQUksQ0FBQyxLQUFLLE9BQU87Q0FFakIsS0FBSyxNQUFNLFNBQVMsSUFBSSxNQUFNLEdBQUcsR0FBRztFQUNsQyxNQUFNLENBQUMsTUFBTSxVQUFVLE1BQU0sTUFBTSxHQUFHLENBQUMsQ0FBQyxLQUFLLE1BQU0sRUFBRSxLQUFLLENBQUM7RUFDM0QsSUFBSSxRQUFRLFFBQVEsSUFBSSxJQUFJLE1BQU0sTUFBTTtDQUMxQztDQUNBLE9BQU87QUFDVDs7QUFHQSxTQUFnQixRQUFRLFdBQWtDO0NBQ3hELElBQUksQ0FBQyxXQUFXLE9BQU87Q0FDdkIsTUFBTSxRQUFRLFVBQVUsUUFBUSxHQUFHO0NBQ25DLE9BQU8sVUFBVSxLQUFLLFlBQVksVUFBVSxNQUFNLFFBQVEsQ0FBQztBQUM3RDs7QUFHQSxTQUFnQixXQUFXLFdBQTBCLFNBQXNDO0NBQ3pGLE1BQU0sU0FBUyxRQUFRLElBQUksUUFBUSxTQUFTLENBQUM7Q0FDN0MsT0FBTyxTQUFTLEtBQUssT0FBTyxLQUFLO0FBQ25DOzs7OztBQU1BLElBQUksU0FBcUM7QUFFekMsU0FBZ0Isa0JBQXVDO0NBQ3JELFdBQVcsa0JBQWtCLFFBQVEsSUFBSSxjQUFjO0NBQ3ZELE9BQU87QUFDVDs7QUFHQSxTQUFnQix5QkFBK0I7Q0FDN0MsU0FBUztBQUNYIn0=
@@ -1,6 +1,7 @@
1
1
  import { getRefreshToken } from "../../utils/providers/common/credentials.js";
2
2
  import { SLACK_COLOR, notifySlack } from "../../utils/slack-notify.js";
3
3
  import { TokenModel } from "../token/token.model.js";
4
+ import { getSlackUserMap, mentionFor } from "./slack-user-map.js";
4
5
  import { getLogger } from "@logtape/logtape";
5
6
  //#region src/application/qgrid/token-death.ts
6
7
  /**
@@ -32,10 +33,16 @@ async function deactivateAuthDeadToken(token, usedRefreshToken, reasonCode) {
32
33
  return false;
33
34
  }
34
35
  logger.warn(`token deactivated (auth dead): ${token.name}(id=${token.id}) reason=${reasonCode}`);
36
+ const mention = mentionFor(token.name, getSlackUserMap());
35
37
  notifySlack({
36
38
  title: "세션 만료",
37
39
  subject: token.name,
38
- context: `${token.provider} · ${reasonCode} · 재로그인이 필요합니다`,
40
+ context: [
41
+ mention,
42
+ token.provider,
43
+ reasonCode,
44
+ "재로그인이 필요합니다"
45
+ ].filter(Boolean).join(" · "),
39
46
  color: SLACK_COLOR.bad
40
47
  });
41
48
  return true;
@@ -54,10 +61,15 @@ function notifyLastActiveTokenDying(token, reasonCode) {
54
61
  const previous = lastActiveAlertAt.get(token.provider);
55
62
  if (previous !== void 0 && now - previous < LAST_ACTIVE_ALERT_COOLDOWN_MS) return;
56
63
  lastActiveAlertAt.set(token.provider, now);
64
+ const mention = mentionFor(token.name, getSlackUserMap());
57
65
  notifySlack({
58
66
  title: "마지막 토큰 사망",
59
67
  subject: token.name,
60
- context: `${token.provider} · ${reasonCode} · 풀이 비지 않도록 유지 중입니다. 전 토큰이 동시에 실패했다면 client_id 취소나 OAuth 계약 변경을 의심하세요`,
68
+ context: [
69
+ mention,
70
+ token.provider,
71
+ reasonCode
72
+ ].filter(Boolean).join(" · ") + " · 풀이 비지 않도록 유지 중입니다. 전 토큰이 동시에 실패했다면 client_id 취소나 OAuth 계약 변경을 의심하세요",
61
73
  color: SLACK_COLOR.bad
62
74
  });
63
75
  }
@@ -67,11 +79,15 @@ function notifyTokenAdded(name, provider) {
67
79
  notifySlack({
68
80
  title: "토큰 추가",
69
81
  subject: name,
70
- context: `${provider} · 요청 처리에 사용됩니다`,
82
+ context: [
83
+ mentionFor(name, getSlackUserMap()),
84
+ provider,
85
+ "요청 처리에 사용됩니다"
86
+ ].filter(Boolean).join(" · "),
71
87
  color: SLACK_COLOR.good
72
88
  });
73
89
  }
74
90
  //#endregion
75
91
  export { deactivateAuthDeadToken, notifyTokenAdded };
76
92
 
77
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW4tZGVhdGguanMiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2FwcGxpY2F0aW9uL3FncmlkL3Rva2VuLWRlYXRoLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICog7IS47IWYIOunjOujjChhdXRoLWRlYWQpIO2GoO2BsOydmCDrnbzsmrDtjIUg7KCc7Jm4IOyngOygkC5cbiAqXG4gKiByZWZyZXNoIOqwgCDsnqzroZzqt7jsnbgg7Jm4IOuzteq1rCDrtojqsIDtlZwg7Iuk7Yyo66W8IOuwmO2ZmO2VnCDsiJzqsITsl5Drp4wg7Zi47Lac65Cc64ukLiDruYTtmZzshLHtmZTripRcbiAqIGBhY3RpdmU9dHJ1ZWAg7KGw6rG067aAIOqwseyLoCAx7ZqM7J206rOgLCDqt7gg6rCx7Iug7J2EIOydtOq4tCDtlITroZzshLjsiqTrp4wgU2xhY2sg7JWM66a87J2EIOuztOuCuOuLpCDigJRcbiAqIOqzteycoCBEQiDrpbwg7JOw64qUIOyduOyKpO2EtOyKpOqwgCDrqofsnbTrk6Ag7IKs6rG064u5IOyVjOumvOydgCAx6rG07J2064ukLlxuICovXG5pbXBvcnQgeyBnZXRMb2dnZXIgfSBmcm9tIFwiQGxvZ3RhcGUvbG9ndGFwZVwiO1xuXG5pbXBvcnQgeyBnZXRSZWZyZXNoVG9rZW4gfSBmcm9tIFwiLi4vLi4vdXRpbHMvcHJvdmlkZXJzL2NvbW1vbi9jcmVkZW50aWFsc1wiO1xuaW1wb3J0IHsgbm90aWZ5U2xhY2ssIFNMQUNLX0NPTE9SIH0gZnJvbSBcIi4uLy4uL3V0aWxzL3NsYWNrLW5vdGlmeVwiO1xuaW1wb3J0IHsgVG9rZW5Nb2RlbCB9IGZyb20gXCIuLi90b2tlbi90b2tlbi5tb2RlbFwiO1xuXG5jb25zdCBsb2dnZXIgPSBnZXRMb2dnZXIoW1wicWdyaWRcIiwgXCJ0b2tlbi1kZWF0aFwiXSk7XG5cbnR5cGUgRGVhZFRva2VuUmVmID0ge1xuICBpZDogbnVtYmVyO1xuICBuYW1lOiBzdHJpbmc7XG4gIHByb3ZpZGVyOiBzdHJpbmc7XG59O1xuXG4vKipcbiAqIEBwYXJhbSB1c2VkUmVmcmVzaFRva2VuIOyLpO2MqO2VnCDsmpTssq3snbQg7Iuk7KCc66GcIOyCrOyaqe2VnCByZWZyZXNoIHRva2VuLlxuICogICByZWZyZXNoIHRva2VuIOydgCDtmozsoIQg7KCA7J6l65CY66+A66GcLCDsnbTrr7gg7ZqM7KCE65CcIOyYmyDthqDtgbDsnLzroZwg64qm6rKMIOuPhOywqe2VnCDsnqzsi5zrj4TqsIBcbiAqICAgNDAwIOydhCDrsJvripQg6rKD7J2AIOygleyDgSDroIjsnbTsiqTri6QuIERCIOydmCDtmITsnqwg6rCS6rO8IOuLpOultOuptCDsgqzrp53snLzroZwg67O07KeAIOyViuuKlOuLpC5cbiAqIEBwYXJhbSByZWFzb25Db2RlIGBwcm92aWRlcjpjb2RlYCDtmJXtg5zsnZgg64K067aAIOy9lOuTnC4gcHJvdmlkZXIg7J2R64u1IOuzuOusuOyXkOuKlCDqs4TsoJUg7J2066mU7J287J2064KYXG4gKiAgIO2GoO2BsCDsobDqsIHsnbQg7ISe7J28IOyImCDsnojslrQgcmF3IOuplOyLnOyngOulvCDqt7jrjIDroZwg7JWM66a87JeQIOyLo+yngCDslYrripTri6QuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBkZWFjdGl2YXRlQXV0aERlYWRUb2tlbihcbiAgdG9rZW46IERlYWRUb2tlblJlZixcbiAgdXNlZFJlZnJlc2hUb2tlbjogc3RyaW5nLFxuICByZWFzb25Db2RlOiBzdHJpbmcsXG4pOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgY29uc3QgY3VycmVudCA9IGF3YWl0IFRva2VuTW9kZWwuZmluZE9uZShcIkFcIiwgeyBpZDogdG9rZW4uaWQgfSk7XG4gIGlmICghY3VycmVudCkgcmV0dXJuIGZhbHNlO1xuXG4gIGNvbnN0IGN1cnJlbnRSZWZyZXNoVG9rZW4gPSBnZXRSZWZyZXNoVG9rZW4oY3VycmVudC5jcmVkZW50aWFscyk7XG4gIGlmIChjdXJyZW50UmVmcmVzaFRva2VuICYmIGN1cnJlbnRSZWZyZXNoVG9rZW4gIT09IHVzZWRSZWZyZXNoVG9rZW4pIHtcbiAgICBsb2dnZXIuaW5mbyhcbiAgICAgIGBza2lwcGluZyBhdXRoLWRlYXRoIGZvciAke3Rva2VuLm5hbWV9KGlkPSR7dG9rZW4uaWR9KTogcmVmcmVzaCB0b2tlbiByb3RhdGVkIHNpbmNlIHRoaXMgYXR0ZW1wdGAsXG4gICAgKTtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICBjb25zdCB7IGRlYWN0aXZhdGVkLCBrZXB0QXNMYXN0QWN0aXZlIH0gPSBhd2FpdCBUb2tlbk1vZGVsLmRlYWN0aXZhdGVJZkFjdGl2ZSh0b2tlbi5pZCk7XG4gIGlmICghZGVhY3RpdmF0ZWQpIHtcbiAgICBpZiAoa2VwdEFzTGFzdEFjdGl2ZSkgbm90aWZ5TGFzdEFjdGl2ZVRva2VuRHlpbmcodG9rZW4sIHJlYXNvbkNvZGUpO1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuXG4gIGxvZ2dlci53YXJuKGB0b2tlbiBkZWFjdGl2YXRlZCAoYXV0aCBkZWFkKTogJHt0b2tlbi5uYW1lfShpZD0ke3Rva2VuLmlkfSkgcmVhc29uPSR7cmVhc29uQ29kZX1gKTtcbiAgdm9pZCBub3RpZnlTbGFjayh7XG4gICAgdGl0bGU6IFwi7IS47IWYIOunjOujjFwiLFxuICAgIHN1YmplY3Q6IHRva2VuLm5hbWUsXG4gICAgY29udGV4dDogYCR7dG9rZW4ucHJvdmlkZXJ9IMK3ICR7cmVhc29uQ29kZX0gwrcg7J6s66Gc6re47J247J20IO2VhOyalO2VqeuLiOuLpGAsXG4gICAgY29sb3I6IFNMQUNLX0NPTE9SLmJhZCxcbiAgfSk7XG4gIHJldHVybiB0cnVlO1xufVxuXG4vKipcbiAqIOuniOyngOuniSDtmZzshLEg7Yag7YGw7J20IOyjveyXiOydhCDrlYzsnZgg7JWM66a8IOy/qOuLpOyatC5cbiAqXG4gKiDsnbQg6rK97JqwIO2GoO2BsOydgCDruYTtmZzshLHtmZTrkJjsp4Ag7JWK6rOgIOyCtOyVhOuCqOycvOuvgOuhnCwg65Kk65Sw66W064qUIOyalOyyreuniOuLpCDqsJnsnYAg7Iuk7Yyo66W8IOuwmOuzte2VtFxuICog7JWM66a87J20IO2PreyjvO2VnOuLpC4g67mE7Zmc7ISx7ZmUIOyEseqztSDqsr3roZzqsIAg7KGw6rG067aAIFVQREFURSDroZwg7KSR67O17J2EIOunieuKlCDqsoPqs7wg64us66asIOyXrOq4sOyXlFxuICog7IOB7YOcIOuzgO2ZlOqwgCDsl4bslrQgREIg66GcIOyWteygnO2VoCDsiJgg7JeG64ukIOKAlCBwcm92aWRlciDri6jsnITroZwg7Iuc6rCEIOywveydhCDrkZTri6QuXG4gKi9cbmNvbnN0IExBU1RfQUNUSVZFX0FMRVJUX0NPT0xET1dOX01TID0gMzAgKiA2MCAqIDEwMDA7XG5jb25zdCBsYXN0QWN0aXZlQWxlcnRBdCA9IG5ldyBNYXA8c3RyaW5nLCBudW1iZXI+KCk7XG5cbmZ1bmN0aW9uIG5vdGlmeUxhc3RBY3RpdmVUb2tlbkR5aW5nKHRva2VuOiBEZWFkVG9rZW5SZWYsIHJlYXNvbkNvZGU6IHN0cmluZyk6IHZvaWQge1xuICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICBjb25zdCBwcmV2aW91cyA9IGxhc3RBY3RpdmVBbGVydEF0LmdldCh0b2tlbi5wcm92aWRlcik7XG4gIGlmIChwcmV2aW91cyAhPT0gdW5kZWZpbmVkICYmIG5vdyAtIHByZXZpb3VzIDwgTEFTVF9BQ1RJVkVfQUxFUlRfQ09PTERPV05fTVMpIHJldHVybjtcbiAgbGFzdEFjdGl2ZUFsZXJ0QXQuc2V0KHRva2VuLnByb3ZpZGVyLCBub3cpO1xuXG4gIHZvaWQgbm90aWZ5U2xhY2soe1xuICAgIHRpdGxlOiBcIuuniOyngOuniSDthqDtgbAg7IKs66edXCIsXG4gICAgc3ViamVjdDogdG9rZW4ubmFtZSxcbiAgICBjb250ZXh0OlxuICAgICAgYCR7dG9rZW4ucHJvdmlkZXJ9IMK3ICR7cmVhc29uQ29kZX0gwrcg7ZKA7J20IOu5hOyngCDslYrrj4TroZ0g7Jyg7KeAIOykkeyeheuLiOuLpC4gYCArXG4gICAgICBg7KCEIO2GoO2BsOydtCDrj5nsi5zsl5Ag7Iuk7Yyo7ZaI64uk66m0IGNsaWVudF9pZCDst6jshozrgpggT0F1dGgg6rOE7JW9IOuzgOqyveydhCDsnZjsi6ztlZjshLjsmpRgLFxuICAgIGNvbG9yOiBTTEFDS19DT0xPUi5iYWQsXG4gIH0pO1xufVxuXG4vKiog66Gc6re47J247J20IOyZhOujjOuPvCDthqDtgbDsnbQg7KCA7J6l65CQ7J2EIOuVjC4g7Iug6recwrfsnqzroZzqt7jsnbjsnYQg6rCA66as7KeAIOyViuuKlOuLpC4gKi9cbmV4cG9ydCBmdW5jdGlvbiBub3RpZnlUb2tlbkFkZGVkKG5hbWU6IHN0cmluZywgcHJvdmlkZXI6IHN0cmluZyk6IHZvaWQge1xuICBsb2dnZXIuaW5mbyhgdG9rZW4gYWRkZWQ6ICR7bmFtZX0gKCR7cHJvdmlkZXJ9KWApO1xuICB2b2lkIG5vdGlmeVNsYWNrKHtcbiAgICB0aXRsZTogXCLthqDtgbAg7LaU6rCAXCIsXG4gICAgc3ViamVjdDogbmFtZSxcbiAgICBjb250ZXh0OiBgJHtwcm92aWRlcn0gwrcg7JqU7LKtIOyymOumrOyXkCDsgqzsmqnrkKnri4jri6RgLFxuICAgIGNvbG9yOiBTTEFDS19DT0xPUi5nb29kLFxuICB9KTtcbn1cbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBYUEsTUFBTSxTQUFTLFVBQVUsQ0FBQyxTQUFTLGFBQWEsQ0FBQzs7Ozs7Ozs7QUFlakQsZUFBc0Isd0JBQ3BCLE9BQ0Esa0JBQ0EsWUFDa0I7Q0FDbEIsTUFBTSxVQUFVLE1BQU0sV0FBVyxRQUFRLEtBQUssRUFBRSxJQUFJLE1BQU0sR0FBRyxDQUFDO0NBQzlELElBQUksQ0FBQyxTQUFTLE9BQU87Q0FFckIsTUFBTSxzQkFBc0IsZ0JBQWdCLFFBQVEsV0FBVztDQUMvRCxJQUFJLHVCQUF1Qix3QkFBd0Isa0JBQWtCO0VBQ25FLE9BQU8sS0FDTCwyQkFBMkIsTUFBTSxLQUFLLE1BQU0sTUFBTSxHQUFHLDRDQUN2RDtFQUNBLE9BQU87Q0FDVDtDQUVBLE1BQU0sRUFBRSxhQUFhLHFCQUFxQixNQUFNLFdBQVcsbUJBQW1CLE1BQU0sRUFBRTtDQUN0RixJQUFJLENBQUMsYUFBYTtFQUNoQixJQUFJLGtCQUFrQiwyQkFBMkIsT0FBTyxVQUFVO0VBQ2xFLE9BQU87Q0FDVDtDQUVBLE9BQU8sS0FBSyxrQ0FBa0MsTUFBTSxLQUFLLE1BQU0sTUFBTSxHQUFHLFdBQVcsWUFBWTtDQUMvRixBQUFLLFlBQVk7RUFDZixPQUFPO0VBQ1AsU0FBUyxNQUFNO0VBQ2YsU0FBUyxHQUFHLE1BQU0sU0FBUyxLQUFLLFdBQVc7RUFDM0MsT0FBTyxZQUFZO0NBQ3JCLENBQUM7Q0FDRCxPQUFPO0FBQ1Q7Ozs7Ozs7O0FBU0EsTUFBTSxnQ0FBZ0MsT0FBVTtBQUNoRCxNQUFNLG9DQUFvQixJQUFJLElBQW9CO0FBRWxELFNBQVMsMkJBQTJCLE9BQXFCLFlBQTBCO0NBQ2pGLE1BQU0sTUFBTSxLQUFLLElBQUk7Q0FDckIsTUFBTSxXQUFXLGtCQUFrQixJQUFJLE1BQU0sUUFBUTtDQUNyRCxJQUFJLGFBQWEsVUFBYSxNQUFNLFdBQVcsK0JBQStCO0NBQzlFLGtCQUFrQixJQUFJLE1BQU0sVUFBVSxHQUFHO0NBRXpDLEFBQUssWUFBWTtFQUNmLE9BQU87RUFDUCxTQUFTLE1BQU07RUFDZixTQUNFLEdBQUcsTUFBTSxTQUFTLEtBQUssV0FBVztFQUVwQyxPQUFPLFlBQVk7Q0FDckIsQ0FBQztBQUNIOztBQUdBLFNBQWdCLGlCQUFpQixNQUFjLFVBQXdCO0NBQ3JFLE9BQU8sS0FBSyxnQkFBZ0IsS0FBSyxJQUFJLFNBQVMsRUFBRTtDQUNoRCxBQUFLLFlBQVk7RUFDZixPQUFPO0VBQ1AsU0FBUztFQUNULFNBQVMsR0FBRyxTQUFTO0VBQ3JCLE9BQU8sWUFBWTtDQUNyQixDQUFDO0FBQ0gifQ==
93
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW4tZGVhdGguanMiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2FwcGxpY2F0aW9uL3FncmlkL3Rva2VuLWRlYXRoLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICog7IS47IWYIOunjOujjChhdXRoLWRlYWQpIO2GoO2BsOydmCDrnbzsmrDtjIUg7KCc7Jm4IOyngOygkC5cbiAqXG4gKiByZWZyZXNoIOqwgCDsnqzroZzqt7jsnbgg7Jm4IOuzteq1rCDrtojqsIDtlZwg7Iuk7Yyo66W8IOuwmO2ZmO2VnCDsiJzqsITsl5Drp4wg7Zi47Lac65Cc64ukLiDruYTtmZzshLHtmZTripRcbiAqIGBhY3RpdmU9dHJ1ZWAg7KGw6rG067aAIOqwseyLoCAx7ZqM7J206rOgLCDqt7gg6rCx7Iug7J2EIOydtOq4tCDtlITroZzshLjsiqTrp4wgU2xhY2sg7JWM66a87J2EIOuztOuCuOuLpCDigJRcbiAqIOqzteycoCBEQiDrpbwg7JOw64qUIOyduOyKpO2EtOyKpOqwgCDrqofsnbTrk6Ag7IKs6rG064u5IOyVjOumvOydgCAx6rG07J2064ukLlxuICovXG5pbXBvcnQgeyBnZXRMb2dnZXIgfSBmcm9tIFwiQGxvZ3RhcGUvbG9ndGFwZVwiO1xuXG5pbXBvcnQgeyBnZXRSZWZyZXNoVG9rZW4gfSBmcm9tIFwiLi4vLi4vdXRpbHMvcHJvdmlkZXJzL2NvbW1vbi9jcmVkZW50aWFsc1wiO1xuaW1wb3J0IHsgbm90aWZ5U2xhY2ssIFNMQUNLX0NPTE9SIH0gZnJvbSBcIi4uLy4uL3V0aWxzL3NsYWNrLW5vdGlmeVwiO1xuaW1wb3J0IHsgVG9rZW5Nb2RlbCB9IGZyb20gXCIuLi90b2tlbi90b2tlbi5tb2RlbFwiO1xuaW1wb3J0IHsgZ2V0U2xhY2tVc2VyTWFwLCBtZW50aW9uRm9yIH0gZnJvbSBcIi4vc2xhY2stdXNlci1tYXBcIjtcblxuY29uc3QgbG9nZ2VyID0gZ2V0TG9nZ2VyKFtcInFncmlkXCIsIFwidG9rZW4tZGVhdGhcIl0pO1xuXG50eXBlIERlYWRUb2tlblJlZiA9IHtcbiAgaWQ6IG51bWJlcjtcbiAgbmFtZTogc3RyaW5nO1xuICBwcm92aWRlcjogc3RyaW5nO1xufTtcblxuLyoqXG4gKiBAcGFyYW0gdXNlZFJlZnJlc2hUb2tlbiDsi6TtjKjtlZwg7JqU7LKt7J20IOyLpOygnOuhnCDsgqzsmqntlZwgcmVmcmVzaCB0b2tlbi5cbiAqICAgcmVmcmVzaCB0b2tlbiDsnYAg7ZqM7KCEIOyggOyepeuQmOuvgOuhnCwg7J2066+4IO2ajOyghOuQnCDsmJsg7Yag7YGw7Jy866GcIOuKpuqyjCDrj4TssKntlZwg7J6s7Iuc64+E6rCAXG4gKiAgIDQwMCDsnYQg67Cb64qUIOqyg+ydgCDsoJXsg4Eg66CI7J207Iqk64ukLiBEQiDsnZgg7ZiE7J6sIOqwkuqzvCDri6TrpbTrqbQg7IKs66ed7Jy866GcIOuztOyngCDslYrripTri6QuXG4gKiBAcGFyYW0gcmVhc29uQ29kZSBgcHJvdmlkZXI6Y29kZWAg7ZiV7YOc7J2YIOuCtOu2gCDsvZTrk5wuIHByb3ZpZGVyIOydkeuLtSDrs7jrrLjsl5DripQg6rOE7KCVIOydtOuplOydvOydtOuCmFxuICogICDthqDtgbAg7KGw6rCB7J20IOyEnuydvCDsiJgg7J6I7Ja0IHJhdyDrqZTsi5zsp4Drpbwg6re464yA66GcIOyVjOumvOyXkCDsi6Psp4Ag7JWK64qU64ukLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZGVhY3RpdmF0ZUF1dGhEZWFkVG9rZW4oXG4gIHRva2VuOiBEZWFkVG9rZW5SZWYsXG4gIHVzZWRSZWZyZXNoVG9rZW46IHN0cmluZyxcbiAgcmVhc29uQ29kZTogc3RyaW5nLFxuKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIGNvbnN0IGN1cnJlbnQgPSBhd2FpdCBUb2tlbk1vZGVsLmZpbmRPbmUoXCJBXCIsIHsgaWQ6IHRva2VuLmlkIH0pO1xuICBpZiAoIWN1cnJlbnQpIHJldHVybiBmYWxzZTtcblxuICBjb25zdCBjdXJyZW50UmVmcmVzaFRva2VuID0gZ2V0UmVmcmVzaFRva2VuKGN1cnJlbnQuY3JlZGVudGlhbHMpO1xuICBpZiAoY3VycmVudFJlZnJlc2hUb2tlbiAmJiBjdXJyZW50UmVmcmVzaFRva2VuICE9PSB1c2VkUmVmcmVzaFRva2VuKSB7XG4gICAgbG9nZ2VyLmluZm8oXG4gICAgICBgc2tpcHBpbmcgYXV0aC1kZWF0aCBmb3IgJHt0b2tlbi5uYW1lfShpZD0ke3Rva2VuLmlkfSk6IHJlZnJlc2ggdG9rZW4gcm90YXRlZCBzaW5jZSB0aGlzIGF0dGVtcHRgLFxuICAgICk7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgY29uc3QgeyBkZWFjdGl2YXRlZCwga2VwdEFzTGFzdEFjdGl2ZSB9ID0gYXdhaXQgVG9rZW5Nb2RlbC5kZWFjdGl2YXRlSWZBY3RpdmUodG9rZW4uaWQpO1xuICBpZiAoIWRlYWN0aXZhdGVkKSB7XG4gICAgaWYgKGtlcHRBc0xhc3RBY3RpdmUpIG5vdGlmeUxhc3RBY3RpdmVUb2tlbkR5aW5nKHRva2VuLCByZWFzb25Db2RlKTtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICBsb2dnZXIud2FybihgdG9rZW4gZGVhY3RpdmF0ZWQgKGF1dGggZGVhZCk6ICR7dG9rZW4ubmFtZX0oaWQ9JHt0b2tlbi5pZH0pIHJlYXNvbj0ke3JlYXNvbkNvZGV9YCk7XG4gIC8vIOunjOujjCDsp4Htm4TqsIAg7J6s66Gc6re47J247ZWY6riwIOqwgOyepSDsoovsnYAg7YOA7J2067CN7J2064ukLiDshozsnKDsnpDrpbwg66mY7IWY7ZW0IOq3uCDsiJzqsIQg64u57IKs7J6Q7JeQ6rKMXG4gIC8vIOq9guuKlOuLpCDigJQg64uk7J207KCc7Iqk7Yq466W8IOq4sOuLpOumrOuptCDri6TsnYwg7KO86riw6rmM7KeAIO2GoO2BsOydtCDruaDsp4Qg7LGE66GcIOuCqOuKlOuLpC5cbiAgY29uc3QgbWVudGlvbiA9IG1lbnRpb25Gb3IodG9rZW4ubmFtZSwgZ2V0U2xhY2tVc2VyTWFwKCkpO1xuICB2b2lkIG5vdGlmeVNsYWNrKHtcbiAgICB0aXRsZTogXCLshLjshZgg66eM66OMXCIsXG4gICAgc3ViamVjdDogdG9rZW4ubmFtZSxcbiAgICBjb250ZXh0OiBbbWVudGlvbiwgdG9rZW4ucHJvdmlkZXIsIHJlYXNvbkNvZGUsIFwi7J6s66Gc6re47J247J20IO2VhOyalO2VqeuLiOuLpFwiXVxuICAgICAgLmZpbHRlcihCb29sZWFuKVxuICAgICAgLmpvaW4oXCIgwrcgXCIpLFxuICAgIGNvbG9yOiBTTEFDS19DT0xPUi5iYWQsXG4gIH0pO1xuICByZXR1cm4gdHJ1ZTtcbn1cblxuLyoqXG4gKiDrp4jsp4Drp4kg7Zmc7ISxIO2GoO2BsOydtCDso73sl4jsnYQg65WM7J2YIOyVjOumvCDsv6jri6TsmrQuXG4gKlxuICog7J20IOqyveyasCDthqDtgbDsnYAg67mE7Zmc7ISx7ZmU65CY7KeAIOyViuqzoCDsgrTslYTrgqjsnLzrr4DroZwsIOuSpOuUsOultOuKlCDsmpTssq3rp4jri6Qg6rCZ7J2AIOyLpO2MqOulvCDrsJjrs7XtlbRcbiAqIOyVjOumvOydtCDtj63so7ztlZzri6QuIOu5hO2ZnOyEse2ZlCDshLHqs7Ug6rK966Gc6rCAIOyhsOqxtOu2gCBVUERBVEUg66GcIOykkeuzteydhCDrp4nripQg6rKD6rO8IOuLrOumrCDsl6zquLDsl5RcbiAqIOyDge2DnCDrs4DtmZTqsIAg7JeG7Ja0IERCIOuhnCDslrXsoJztlaAg7IiYIOyXhuuLpCDigJQgcHJvdmlkZXIg64uo7JyE66GcIOyLnOqwhCDssL3snYQg65GU64ukLlxuICovXG5jb25zdCBMQVNUX0FDVElWRV9BTEVSVF9DT09MRE9XTl9NUyA9IDMwICogNjAgKiAxMDAwO1xuY29uc3QgbGFzdEFjdGl2ZUFsZXJ0QXQgPSBuZXcgTWFwPHN0cmluZywgbnVtYmVyPigpO1xuXG5mdW5jdGlvbiBub3RpZnlMYXN0QWN0aXZlVG9rZW5EeWluZyh0b2tlbjogRGVhZFRva2VuUmVmLCByZWFzb25Db2RlOiBzdHJpbmcpOiB2b2lkIHtcbiAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcbiAgY29uc3QgcHJldmlvdXMgPSBsYXN0QWN0aXZlQWxlcnRBdC5nZXQodG9rZW4ucHJvdmlkZXIpO1xuICBpZiAocHJldmlvdXMgIT09IHVuZGVmaW5lZCAmJiBub3cgLSBwcmV2aW91cyA8IExBU1RfQUNUSVZFX0FMRVJUX0NPT0xET1dOX01TKSByZXR1cm47XG4gIGxhc3RBY3RpdmVBbGVydEF0LnNldCh0b2tlbi5wcm92aWRlciwgbm93KTtcblxuICBjb25zdCBtZW50aW9uID0gbWVudGlvbkZvcih0b2tlbi5uYW1lLCBnZXRTbGFja1VzZXJNYXAoKSk7XG4gIHZvaWQgbm90aWZ5U2xhY2soe1xuICAgIHRpdGxlOiBcIuuniOyngOuniSDthqDtgbAg7IKs66edXCIsXG4gICAgc3ViamVjdDogdG9rZW4ubmFtZSxcbiAgICBjb250ZXh0OlxuICAgICAgW21lbnRpb24sIHRva2VuLnByb3ZpZGVyLCByZWFzb25Db2RlXS5maWx0ZXIoQm9vbGVhbikuam9pbihcIiDCtyBcIikgK1xuICAgICAgYCDCtyDtkoDsnbQg67mE7KeAIOyViuuPhOuhnSDsnKDsp4Ag7KSR7J6F64uI64ukLiBgICtcbiAgICAgIGDsoIQg7Yag7YGw7J20IOuPmeyLnOyXkCDsi6TtjKjtlojri6TrqbQgY2xpZW50X2lkIOy3qOyGjOuCmCBPQXV0aCDqs4Tslb0g67OA6rK97J2EIOydmOyLrO2VmOyEuOyalGAsXG4gICAgY29sb3I6IFNMQUNLX0NPTE9SLmJhZCxcbiAgfSk7XG59XG5cbi8qKiDroZzqt7jsnbjsnbQg7JmE66OM64+8IO2GoO2BsOydtCDsoIDsnqXrkJDsnYQg65WMLiDsi6Dqt5zCt+yerOuhnOq3uOyduOydhCDqsIDrpqzsp4Ag7JWK64qU64ukLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG5vdGlmeVRva2VuQWRkZWQobmFtZTogc3RyaW5nLCBwcm92aWRlcjogc3RyaW5nKTogdm9pZCB7XG4gIGxvZ2dlci5pbmZvKGB0b2tlbiBhZGRlZDogJHtuYW1lfSAoJHtwcm92aWRlcn0pYCk7XG4gIGNvbnN0IG1lbnRpb24gPSBtZW50aW9uRm9yKG5hbWUsIGdldFNsYWNrVXNlck1hcCgpKTtcbiAgdm9pZCBub3RpZnlTbGFjayh7XG4gICAgdGl0bGU6IFwi7Yag7YGwIOy2lOqwgFwiLFxuICAgIHN1YmplY3Q6IG5hbWUsXG4gICAgY29udGV4dDogW21lbnRpb24sIHByb3ZpZGVyLCBcIuyalOyyrSDsspjrpqzsl5Ag7IKs7Jqp65Cp64uI64ukXCJdLmZpbHRlcihCb29sZWFuKS5qb2luKFwiIMK3IFwiKSxcbiAgICBjb2xvcjogU0xBQ0tfQ09MT1IuZ29vZCxcbiAgfSk7XG59XG4iXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7QUFjQSxNQUFNLFNBQVMsVUFBVSxDQUFDLFNBQVMsYUFBYSxDQUFDOzs7Ozs7OztBQWVqRCxlQUFzQix3QkFDcEIsT0FDQSxrQkFDQSxZQUNrQjtDQUNsQixNQUFNLFVBQVUsTUFBTSxXQUFXLFFBQVEsS0FBSyxFQUFFLElBQUksTUFBTSxHQUFHLENBQUM7Q0FDOUQsSUFBSSxDQUFDLFNBQVMsT0FBTztDQUVyQixNQUFNLHNCQUFzQixnQkFBZ0IsUUFBUSxXQUFXO0NBQy9ELElBQUksdUJBQXVCLHdCQUF3QixrQkFBa0I7RUFDbkUsT0FBTyxLQUNMLDJCQUEyQixNQUFNLEtBQUssTUFBTSxNQUFNLEdBQUcsNENBQ3ZEO0VBQ0EsT0FBTztDQUNUO0NBRUEsTUFBTSxFQUFFLGFBQWEscUJBQXFCLE1BQU0sV0FBVyxtQkFBbUIsTUFBTSxFQUFFO0NBQ3RGLElBQUksQ0FBQyxhQUFhO0VBQ2hCLElBQUksa0JBQWtCLDJCQUEyQixPQUFPLFVBQVU7RUFDbEUsT0FBTztDQUNUO0NBRUEsT0FBTyxLQUFLLGtDQUFrQyxNQUFNLEtBQUssTUFBTSxNQUFNLEdBQUcsV0FBVyxZQUFZO0NBRy9GLE1BQU0sVUFBVSxXQUFXLE1BQU0sTUFBTSxnQkFBZ0IsQ0FBQztDQUN4RCxBQUFLLFlBQVk7RUFDZixPQUFPO0VBQ1AsU0FBUyxNQUFNO0VBQ2YsU0FBUztHQUFDO0dBQVMsTUFBTTtHQUFVO0dBQVk7RUFBYSxDQUFDLENBQzFELE9BQU8sT0FBTyxDQUFDLENBQ2YsS0FBSyxLQUFLO0VBQ2IsT0FBTyxZQUFZO0NBQ3JCLENBQUM7Q0FDRCxPQUFPO0FBQ1Q7Ozs7Ozs7O0FBU0EsTUFBTSxnQ0FBZ0MsT0FBVTtBQUNoRCxNQUFNLG9DQUFvQixJQUFJLElBQW9CO0FBRWxELFNBQVMsMkJBQTJCLE9BQXFCLFlBQTBCO0NBQ2pGLE1BQU0sTUFBTSxLQUFLLElBQUk7Q0FDckIsTUFBTSxXQUFXLGtCQUFrQixJQUFJLE1BQU0sUUFBUTtDQUNyRCxJQUFJLGFBQWEsVUFBYSxNQUFNLFdBQVcsK0JBQStCO0NBQzlFLGtCQUFrQixJQUFJLE1BQU0sVUFBVSxHQUFHO0NBRXpDLE1BQU0sVUFBVSxXQUFXLE1BQU0sTUFBTSxnQkFBZ0IsQ0FBQztDQUN4RCxBQUFLLFlBQVk7RUFDZixPQUFPO0VBQ1AsU0FBUyxNQUFNO0VBQ2YsU0FDRTtHQUFDO0dBQVMsTUFBTTtHQUFVO0VBQVUsQ0FBQyxDQUFDLE9BQU8sT0FBTyxDQUFDLENBQUMsS0FBSyxLQUFLLElBQ2hFO0VBRUYsT0FBTyxZQUFZO0NBQ3JCLENBQUM7QUFDSDs7QUFHQSxTQUFnQixpQkFBaUIsTUFBYyxVQUF3QjtDQUNyRSxPQUFPLEtBQUssZ0JBQWdCLEtBQUssSUFBSSxTQUFTLEVBQUU7Q0FFaEQsQUFBSyxZQUFZO0VBQ2YsT0FBTztFQUNQLFNBQVM7RUFDVCxTQUFTO0dBSkssV0FBVyxNQUFNLGdCQUFnQixDQUlyQztHQUFTO0dBQVU7RUFBYyxDQUFDLENBQUMsT0FBTyxPQUFPLENBQUMsQ0FBQyxLQUFLLEtBQUs7RUFDdkUsT0FBTyxZQUFZO0NBQ3JCLENBQUM7QUFDSCJ9
@@ -1,6 +1,6 @@
1
1
  import { monitLogSink } from "./application/monit/log-buffer.js";
2
2
  import { QgridDispatcher } from "./application/qgrid/qgrid.dispatcher.js";
3
- import { startExpiredTokenDigest, stopExpiredTokenDigest } from "./application/qgrid/expired-token-digest.js";
3
+ import { startExpiredTokenReminder, stopExpiredTokenReminder } from "./application/qgrid/expired-token-reminder.js";
4
4
  import { QgridFrame } from "./application/qgrid/qgrid.frame.js";
5
5
  import { TokenSubscriber } from "./application/qgrid/token-subscriber.js";
6
6
  import { ensureTokensTrigger } from "./application/qgrid/token-trigger-setup.js";
@@ -161,7 +161,7 @@ var sonamu_config_default = defineConfig({
161
161
  QgridDispatcher.startupState.anthropic = "failed";
162
162
  log.warn(`anthropic dispatcher failed: ${e.message}`);
163
163
  }
164
- startExpiredTokenDigest();
164
+ startExpiredTokenReminder();
165
165
  const anthropicCount = QgridDispatcher.anthropicDispatcher?.tokenCount ?? 0;
166
166
  const openaiReady = QgridDispatcher.openaiDispatcher?.readyWorkerCount ?? 0;
167
167
  const openaiTotal = QgridDispatcher.openaiDispatcher?.workerCount ?? 0;
@@ -172,7 +172,7 @@ var sonamu_config_default = defineConfig({
172
172
  },
173
173
  onShutdown: async () => {
174
174
  const log = getLogger(["qgrid", "startup"]);
175
- stopExpiredTokenDigest();
175
+ stopExpiredTokenReminder();
176
176
  if (QgridDispatcher.openaiDispatcher) await QgridDispatcher.openaiDispatcher.stop();
177
177
  if (QgridDispatcher.anthropicDispatcher) await QgridDispatcher.anthropicDispatcher.stop();
178
178
  if (QgridDispatcher.subscriber) await QgridDispatcher.subscriber.stop();
@@ -187,4 +187,4 @@ var sonamu_config_default = defineConfig({
187
187
  //#endregion
188
188
  export { sonamu_config_default as default };
189
189
 
190
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic29uYW11LmNvbmZpZy5qcyIsIm5hbWVzIjpbImNhY2hlRHJpdmVycyJdLCJzb3VyY2VzIjpbIi4uL3NyYy9zb25hbXUuY29uZmlnLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBwYXRoIGZyb20gXCJwYXRoXCI7XG5cbmltcG9ydCB7IGdldENvbnNvbGVTaW5rIH0gZnJvbSBcIkBsb2d0YXBlL2xvZ3RhcGVcIjtcbmltcG9ydCB7IGdldExvZ2dlciB9IGZyb20gXCJAbG9ndGFwZS9sb2d0YXBlXCI7XG5pbXBvcnQgeyBnZXRQcmV0dHlGb3JtYXR0ZXIgfSBmcm9tIFwiQGxvZ3RhcGUvcHJldHR5XCI7XG5pbXBvcnQgeyBDYWNoZVByZXNldHMsIGRlZmluZUNvbmZpZyB9IGZyb20gXCJzb25hbXVcIjtcbmltcG9ydCB7IGRyaXZlcnMgYXMgY2FjaGVEcml2ZXJzLCBzdG9yZSB9IGZyb20gXCJzb25hbXUvY2FjaGVcIjtcblxuaW1wb3J0IHsgbW9uaXRMb2dTaW5rIH0gZnJvbSBcIi4vYXBwbGljYXRpb24vbW9uaXQvbG9nLWJ1ZmZlclwiO1xuaW1wb3J0IHtcbiAgc3RhcnRFeHBpcmVkVG9rZW5EaWdlc3QsXG4gIHN0b3BFeHBpcmVkVG9rZW5EaWdlc3QsXG59IGZyb20gXCIuL2FwcGxpY2F0aW9uL3FncmlkL2V4cGlyZWQtdG9rZW4tZGlnZXN0XCI7XG5pbXBvcnQgeyBRZ3JpZERpc3BhdGNoZXIgfSBmcm9tIFwiLi9hcHBsaWNhdGlvbi9xZ3JpZC9xZ3JpZC5kaXNwYXRjaGVyXCI7XG5pbXBvcnQgeyBRZ3JpZEZyYW1lIH0gZnJvbSBcIi4vYXBwbGljYXRpb24vcWdyaWQvcWdyaWQuZnJhbWVcIjtcbmltcG9ydCB7IFRva2VuU3Vic2NyaWJlciB9IGZyb20gXCIuL2FwcGxpY2F0aW9uL3FncmlkL3Rva2VuLXN1YnNjcmliZXJcIjtcbmltcG9ydCB7IGVuc3VyZVRva2Vuc1RyaWdnZXIgfSBmcm9tIFwiLi9hcHBsaWNhdGlvbi9xZ3JpZC90b2tlbi10cmlnZ2VyLXNldHVwXCI7XG5pbXBvcnQgeyBoYW5kbGVTZXJ2ZXJFcnJvciB9IGZyb20gXCIuL3NlcnZlci1lcnJvci1oYW5kbGVyXCI7XG5pbXBvcnQgeyBBbnRocm9waWNEaXNwYXRjaGVyIH0gZnJvbSBcIi4vdXRpbHMvcHJvdmlkZXJzL2FudGhyb3BpYy9hbnRocm9waWMtZGlzcGF0Y2hlclwiO1xuaW1wb3J0IHsgT3BlbkFJRGlzcGF0Y2hlciB9IGZyb20gXCIuL3V0aWxzL3Byb3ZpZGVycy9vcGVuYWkvb3BlbmFpLWRpc3BhdGNoZXJcIjtcblxuY29uc3QgaG9zdCA9IHByb2Nlc3MuZW52LkhPU1QgPz8gXCJsb2NhbGhvc3RcIjtcbmNvbnN0IHBvcnQgPSBOdW1iZXIocHJvY2Vzcy5lbnYuUE9SVCA/PyA0NDkwMCk7XG5cbmNvbnN0IGNvbm5Db25maWcgPSB7XG4gIGhvc3Q6IHByb2Nlc3MuZW52LlNPTkFNVV9EQl9IT1NUID8/IFwibG9jYWxob3N0XCIsXG4gIHBvcnQ6IE51bWJlcihwcm9jZXNzLmVudi5TT05BTVVfREJfUE9SVCA/PyA1NDMyKSxcbiAgdXNlcjogcHJvY2Vzcy5lbnYuU09OQU1VX0RCX1VTRVIgPz8gXCJwb3N0Z3Jlc1wiLFxuICBwYXNzd29yZDogcHJvY2Vzcy5lbnYuU09OQU1VX0RCX1BBU1NXT1JEID8/IFwicG9zdGdyZXNcIixcbiAgZGF0YWJhc2U6IHByb2Nlc3MuZW52LlNPTkFNVV9EQl9OQU1FID8/IFwicWdyaWRcIixcbn07XG5cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHByb2plY3ROYW1lOiBwcm9jZXNzLmVudi5QUk9KRUNUX05BTUUgPz8gXCJRZ3JpZFwiLFxuICBkYXRhYmFzZToge1xuICAgIGRlZmF1bHRPcHRpb25zOiB7XG4gICAgICBjb25uZWN0aW9uOiB7XG4gICAgICAgIC4uLmNvbm5Db25maWcsXG4gICAgICAgIGtlZXBBbGl2ZTogdHJ1ZSxcbiAgICAgICAga2VlcEFsaXZlSW5pdGlhbERlbGF5TWlsbGlzOiAxMDAwMCxcbiAgICAgIH0sXG4gICAgfSxcbiAgfSxcbiAgYXBpOiB7XG4gICAgZGlyOiBcImFwaVwiLFxuICAgIHRpbWV6b25lOiBcIkFzaWEvU2VvdWxcIixcbiAgICByb3V0ZToge1xuICAgICAgcHJlZml4OiBcIi9hcGlcIixcbiAgICB9LFxuICB9LFxuICBpMThuOiB7XG4gICAgZGVmYXVsdExvY2FsZTogXCJrb1wiLFxuICAgIHN1cHBvcnRlZExvY2FsZXM6IFtcImtvXCIsIFwiZW5cIl0sXG4gIH0sXG4gIHN5bmM6IHtcbiAgICB0YXJnZXRzOiBbXCJ3ZWJcIl0sXG4gIH0sXG4gIHRlc3Q6IHtcbiAgICBwYXJhbGxlbDogdHJ1ZSxcbiAgICBtYXhXb3JrZXJzOiA0LFxuICAgIGRldlJ1bm5lcjogeyBlbmFibGVkOiB0cnVlIH0sXG4gIH0sXG4gIGxvZ2dpbmc6IHtcbiAgICBzaW5rczoge1xuICAgICAgY29uc29sZTogZ2V0Q29uc29sZVNpbmsoe1xuICAgICAgICBmb3JtYXR0ZXI6IGdldFByZXR0eUZvcm1hdHRlcih7XG4gICAgICAgICAgdGltZXN0YW1wOiBcInRpbWVcIixcbiAgICAgICAgICBjYXRlZ29yeVdpZHRoOiAyMCxcbiAgICAgICAgICBjYXRlZ29yeVRydW5jYXRlOiBcIm1pZGRsZVwiLFxuICAgICAgICB9KSxcbiAgICAgIH0pLFxuICAgICAgLy8gTW9uaXQg7YOt7JqpIGluLW1lbW9yeSByaW5nIGJ1ZmZlci4g7Y+s66e37J2AIHNpbmsg67OEIOKAlCDsvZjshpQg7Lac66Cl7J2AIOq3uOuMgOuhnOuLpC5cbiAgICAgIG1vbml0OiBtb25pdExvZ1NpbmssXG4gICAgfSxcbiAgICBsb2dnZXJzOiBbXG4gICAgICAvLyBxZ3JpZCDroZzqt7ggKOyalOyyrSDrtoTrsLAsIG9hdXRoL3VzYWdlIEFQSSlcbiAgICAgIHsgY2F0ZWdvcnk6IFtcInFncmlkXCJdLCBzaW5rczogW1wiY29uc29sZVwiLCBcIm1vbml0XCJdLCBsb3dlc3RMZXZlbDogXCJpbmZvXCIgfSxcbiAgICAgIC8vIHNvbmFtdSDroZzqt7jripQgd2FybmluZyDsnbTsg4Hrp4xcbiAgICAgIHsgY2F0ZWdvcnk6IFtcInNvbmFtdVwiXSwgc2lua3M6IFtcImNvbnNvbGVcIiwgXCJtb25pdFwiXSwgbG93ZXN0TGV2ZWw6IFwid2FybmluZ1wiIH0sXG4gICAgICAvLyBmYXN0aWZ5IOuhnOq3uCDssKjri6hcbiAgICAgIHsgY2F0ZWdvcnk6IFtcImZhc3RpZnlcIl0sIHNpbmtzOiBbXSwgbG93ZXN0TGV2ZWw6IFwiZmF0YWxcIiB9LFxuICAgIF0sXG4gIH0sXG4gIHNlcnZlcjoge1xuICAgIGxpc3RlbjogeyBwb3J0LCBob3N0IH0sXG4gICAgZmFzdGlmeToge1xuICAgICAgYm9keUxpbWl0OiAxMCAqIDEwMjQgKiAxMDI0LFxuICAgIH0sXG4gICAgcGx1Z2luczoge1xuICAgICAgZm9ybWJvZHk6IHRydWUsXG4gICAgICBxczogdHJ1ZSxcbiAgICAgIHNzZTogdHJ1ZSxcbiAgICAgIG11bHRpcGFydDogeyBsaW1pdHM6IHsgZmlsZVNpemU6IDEwMjQgKiAxMDI0ICogMzAgfSB9LFxuICAgICAgc3RhdGljOiB7XG4gICAgICAgIHJvb3Q6IHBhdGguam9pbihpbXBvcnQubWV0YS5kaXJuYW1lLCBcIi8uLi9cIiwgXCJwdWJsaWNcIiksXG4gICAgICAgIHByZWZpeDogXCIvYXBpL3B1YmxpY1wiLFxuICAgICAgfSxcbiAgICAgIGN1c3RvbTogKHNlcnZlcikgPT4ge1xuICAgICAgICAvLyBPQXV0aCDsvZzrsLEg4oCUIEFudGhyb3BpY+ydtCAvY2FsbGJhY2vsnLzroZwg66as64uk7J2066CJ7Yq4XG4gICAgICAgIHNlcnZlci5nZXQoXCIvY2FsbGJhY2tcIiwgYXN5bmMgKHJlcXVlc3QsIHJlcGx5KSA9PiB7XG4gICAgICAgICAgY29uc3QgeyBjb2RlLCBzdGF0ZSB9ID0gcmVxdWVzdC5xdWVyeSBhcyB7IGNvZGU/OiBzdHJpbmc7IHN0YXRlPzogc3RyaW5nIH07XG4gICAgICAgICAgaWYgKCFjb2RlIHx8ICFzdGF0ZSkge1xuICAgICAgICAgICAgcmV0dXJuIHJlcGx5LnJlZGlyZWN0KFwiLz9vYXV0aD1lcnJvciZyZWFzb249bWlzc2luZ19wYXJhbXNcIik7XG4gICAgICAgICAgfVxuICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICByZXR1cm4gUWdyaWRGcmFtZS5oYW5kbGVPQXV0aENhbGxiYWNrKGNvZGUsIHN0YXRlLCByZXBseSk7XG4gICAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgcmV0dXJuIHJlcGx5LnJlZGlyZWN0KFxuICAgICAgICAgICAgICBgLz9vYXV0aD1lcnJvciZyZWFzb249JHtlbmNvZGVVUklDb21wb25lbnQoKGUgYXMgRXJyb3IpLm1lc3NhZ2UpfWAsXG4gICAgICAgICAgICApO1xuICAgICAgICAgIH1cbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gT3BlbkFJIE9BdXRoIOuKlCBjb2RleCBhcHAtc2VydmVyIOqwgCDsnpDssrQgY2FsbGJhY2sg7ISc67KE66W8IOyYrOumvFxuICAgICAgICAvLyBoYW5kbGVPcGVuQUlDYWxsYmFjayDrtojtlYTsmpQg4oCUIG9hdXRoQ29tcGxldGVPcGVuQUkgQVBJIOuhnCDsmYTro4wg7ZmV7J24XG4gICAgICB9LFxuICAgIH0sXG4gICAgYXBpQ29uZmlnOiB7XG4gICAgICBjb250ZXh0UHJvdmlkZXI6IChkZWZhdWx0Q29udGV4dCwgcmVxdWVzdCkgPT4ge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIC4uLmRlZmF1bHRDb250ZXh0LFxuICAgICAgICAgIGlwOiByZXF1ZXN0LmlwLFxuICAgICAgICAgIGJvZHk6IHJlcXVlc3QuYm9keSxcbiAgICAgICAgfTtcbiAgICAgIH0sXG4gICAgICBndWFyZEhhbmRsZXI6ICgpID0+IHt9LFxuICAgICAgY2FjaGVDb250cm9sSGFuZGxlcjogKHJlcSkgPT4ge1xuICAgICAgICBzd2l0Y2ggKHJlcS50eXBlKSB7XG4gICAgICAgICAgY2FzZSBcImFzc2V0c1wiOlxuICAgICAgICAgICAgLy8gSGFzaCDtj6ztlajrkJwg7YyM7J28OiDsmIHqtawg7LqQ7IucXG4gICAgICAgICAgICBpZiAocmVxLnBhdGgubWF0Y2goLy1bYS1mMC05XStcXC4vKSkge1xuICAgICAgICAgICAgICByZXR1cm4gQ2FjaGVQcmVzZXRzLmltbXV0YWJsZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiBDYWNoZVByZXNldHMubG9uZ0xpdmVkO1xuXG4gICAgICAgICAgY2FzZSBcImFwaVwiOlxuICAgICAgICAgICAgLy8gR0VUIOyalOyyreunjCDsupDsi7Eg6rOg66CkXG4gICAgICAgICAgICBpZiAocmVxLm1ldGhvZCA9PT0gXCJHRVRcIikge1xuICAgICAgICAgICAgICAvLyDtirnsoJUg6rK966Gc64qUIOynp+ydgCDsupDsi5xcbiAgICAgICAgICAgICAgaWYgKHJlcS5wYXRoLnN0YXJ0c1dpdGgoXCIvYXBpL3N0YXRpYy1kYXRhXCIpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIENhY2hlUHJlc2V0cy5zaG9ydExpdmVkO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGlmIChyZXEucGF0aC5zdGFydHNXaXRoKFwiL2FwaS90ZXJtc1wiKSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBDYWNoZVByZXNldHMubWVkaXVtTGl2ZWQ7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIOq4sOuzuDog7LqQ7IucIOyXhuydjFxuICAgICAgICAgICAgcmV0dXJuIENhY2hlUHJlc2V0cy5ub0NhY2hlO1xuXG4gICAgICAgICAgY2FzZSBcInNzclwiOlxuICAgICAgICAgICAgLy8gU1NSIO2OmOydtOyngDogMTDstIgg7LqQ7IucXG4gICAgICAgICAgICByZXR1cm4gQ2FjaGVQcmVzZXRzLnNzcjtcblxuICAgICAgICAgIGNhc2UgXCJjc3JcIjpcbiAgICAgICAgICAgIC8vIENTUiBmYWxsYmFjayAoaW5kZXguaHRtbCk6IDHrtoQg7LqQ7IucXG4gICAgICAgICAgICByZXR1cm4gQ2FjaGVQcmVzZXRzLnNob3J0TGl2ZWQ7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIENhY2hlUHJlc2V0cy5ub0NhY2hlO1xuICAgICAgfSxcbiAgICB9LFxuICAgIGNhY2hlOiB7XG4gICAgICBkZWZhdWx0OiBcIm1haW5cIixcbiAgICAgIHN0b3Jlczoge1xuICAgICAgICBtYWluOiBzdG9yZSgpLnVzZUwxTGF5ZXIoY2FjaGVEcml2ZXJzLm1lbW9yeSh7IG1heFNpemU6IFwiNTBtYlwiIH0pKSxcbiAgICAgIH0sXG4gICAgICB0dGw6IFwiNW1cIixcbiAgICAgIHByZWZpeDogXCJcIixcbiAgICB9LFxuICAgIGxpZmVjeWNsZToge1xuICAgICAgb25TdGFydDogYXN5bmMgKCkgPT4ge1xuICAgICAgICBjb25zdCBsb2cgPSBnZXRMb2dnZXIoW1wicWdyaWRcIiwgXCJzdGFydHVwXCJdKTtcblxuICAgICAgICBsZXQgdHJpZ2dlclJlYWR5ID0gdHJ1ZTtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBhd2FpdCBlbnN1cmVUb2tlbnNUcmlnZ2VyKGNvbm5Db25maWcpO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgdHJpZ2dlclJlYWR5ID0gZmFsc2U7XG4gICAgICAgICAgbG9nLndhcm4oYHRyaWdnZXIgc2V0dXAgZmFpbGVkOiAkeyhlIGFzIEVycm9yKS5tZXNzYWdlfWApO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3Qgc3Vic2NyaWJlciA9IG5ldyBUb2tlblN1YnNjcmliZXIoY29ubkNvbmZpZywgUWdyaWREaXNwYXRjaGVyKTtcbiAgICAgICAgUWdyaWREaXNwYXRjaGVyLnN1YnNjcmliZXIgPSBzdWJzY3JpYmVyO1xuXG4gICAgICAgIGNvbnN0IHN0YXJ0ZWQgPSBhd2FpdCBzdWJzY3JpYmVyLnN0YXJ0KCk7XG5cbiAgICAgICAgLy8g6riw64+ZIOyDge2DnOulvCDrgqjquLTri6Qg4oCUIEhUVFAg64qUIOydtOuvuCDsl7TroKQg7J6I7Ja07IScLCDspIDruYQg7KCEIOyalOyyreydtCBcIuyeoOyLnCDtm4Qg65CY64qU7KeAXCJcbiAgICAgICAgLy8gXCLsnqzsi5zrj4TtlbTrj4Qg7JWIIOuQmOuKlOyngFwi66W8IOq1rOu2hO2VtCDsnZHri7XtlbTslbwg7ZWc64ukKDUwMyB2cyA1MDApLlxuICAgICAgICB0cnkge1xuICAgICAgICAgIGNvbnN0IG9wZW5haURpc3BhdGNoZXIgPSBuZXcgT3BlbkFJRGlzcGF0Y2hlcigpO1xuICAgICAgICAgIGF3YWl0IG9wZW5haURpc3BhdGNoZXIuc3RhcnQoKTtcbiAgICAgICAgICBRZ3JpZERpc3BhdGNoZXIub3BlbmFpRGlzcGF0Y2hlciA9IG9wZW5haURpc3BhdGNoZXI7XG4gICAgICAgICAgUWdyaWREaXNwYXRjaGVyLnN0YXJ0dXBTdGF0ZS5vcGVuYWkgPSBcInJlYWR5XCI7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICBRZ3JpZERpc3BhdGNoZXIuc3RhcnR1cFN0YXRlLm9wZW5haSA9IFwiZmFpbGVkXCI7XG4gICAgICAgICAgbG9nLndhcm4oYG9wZW5haSBkaXNwYXRjaGVyIGZhaWxlZDogJHsoZSBhcyBFcnJvcikubWVzc2FnZX1gKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgY29uc3QgYW50aHJvcGljRGlzcGF0Y2hlciA9IG5ldyBBbnRocm9waWNEaXNwYXRjaGVyKCk7XG4gICAgICAgICAgYXdhaXQgYW50aHJvcGljRGlzcGF0Y2hlci5zdGFydCgpO1xuICAgICAgICAgIFFncmlkRGlzcGF0Y2hlci5hbnRocm9waWNEaXNwYXRjaGVyID0gYW50aHJvcGljRGlzcGF0Y2hlcjtcbiAgICAgICAgICBRZ3JpZERpc3BhdGNoZXIuc3RhcnR1cFN0YXRlLmFudGhyb3BpYyA9IFwicmVhZHlcIjtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgIFFncmlkRGlzcGF0Y2hlci5zdGFydHVwU3RhdGUuYW50aHJvcGljID0gXCJmYWlsZWRcIjtcbiAgICAgICAgICBsb2cud2FybihgYW50aHJvcGljIGRpc3BhdGNoZXIgZmFpbGVkOiAkeyhlIGFzIEVycm9yKS5tZXNzYWdlfWApO1xuICAgICAgICB9XG5cbiAgICAgICAgc3RhcnRFeHBpcmVkVG9rZW5EaWdlc3QoKTtcblxuICAgICAgICBjb25zdCBhbnRocm9waWNDb3VudCA9IFFncmlkRGlzcGF0Y2hlci5hbnRocm9waWNEaXNwYXRjaGVyPy50b2tlbkNvdW50ID8/IDA7XG4gICAgICAgIGNvbnN0IG9wZW5haVJlYWR5ID0gUWdyaWREaXNwYXRjaGVyLm9wZW5haURpc3BhdGNoZXI/LnJlYWR5V29ya2VyQ291bnQgPz8gMDtcbiAgICAgICAgY29uc3Qgb3BlbmFpVG90YWwgPSBRZ3JpZERpc3BhdGNoZXIub3BlbmFpRGlzcGF0Y2hlcj8ud29ya2VyQ291bnQgPz8gMDtcblxuICAgICAgICBsb2cuaW5mbyhgbGlzdGVuaW5nIG9uIGh0dHA6Ly8ke2hvc3R9OiR7cG9ydH1gKTtcbiAgICAgICAgbG9nLmluZm8oYGFudGhyb3BpYzogJHthbnRocm9waWNDb3VudH0gdG9rZW5zIHJlYWR5YCk7XG4gICAgICAgIGxvZy5pbmZvKGBvcGVuYWk6ICR7b3BlbmFpUmVhZHl9LyR7b3BlbmFpVG90YWx9IHRva2VucyByZWFkeWApO1xuICAgICAgICBsb2cuaW5mbyhcbiAgICAgICAgICBgc3Vic2NyaWJlcjogJHtzdGFydGVkID8gXCJMSVNURU4gYWN0aXZlXCIgOiBcImRlZ3JhZGVkXCJ9JHt0cmlnZ2VyUmVhZHkgPyBcIlwiIDogXCIsIHRyaWdnZXIgZmFpbGVkXCJ9YCxcbiAgICAgICAgKTtcbiAgICAgIH0sXG4gICAgICBvblNodXRkb3duOiBhc3luYyAoKSA9PiB7XG4gICAgICAgIGNvbnN0IGxvZyA9IGdldExvZ2dlcihbXCJxZ3JpZFwiLCBcInN0YXJ0dXBcIl0pO1xuICAgICAgICBzdG9wRXhwaXJlZFRva2VuRGlnZXN0KCk7XG4gICAgICAgIGlmIChRZ3JpZERpc3BhdGNoZXIub3BlbmFpRGlzcGF0Y2hlcikge1xuICAgICAgICAgIGF3YWl0IFFncmlkRGlzcGF0Y2hlci5vcGVuYWlEaXNwYXRjaGVyLnN0b3AoKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoUWdyaWREaXNwYXRjaGVyLmFudGhyb3BpY0Rpc3BhdGNoZXIpIHtcbiAgICAgICAgICBhd2FpdCBRZ3JpZERpc3BhdGNoZXIuYW50aHJvcGljRGlzcGF0Y2hlci5zdG9wKCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKFFncmlkRGlzcGF0Y2hlci5zdWJzY3JpYmVyKSB7XG4gICAgICAgICAgYXdhaXQgUWdyaWREaXNwYXRjaGVyLnN1YnNjcmliZXIuc3RvcCgpO1xuICAgICAgICB9XG4gICAgICAgIGxvZy5pbmZvKFwiZ3JhY2VmdWwgc2h1dGRvd25cIik7XG4gICAgICB9LFxuICAgICAgb25FcnJvcjogKGVycm9yLCBfcmVxdWVzdCwgcmVwbHkpID0+IHtcbiAgICAgICAgaGFuZGxlU2VydmVyRXJyb3IoZXJyb3IsIHJlcGx5KTtcbiAgICAgIH0sXG4gICAgfSxcbiAgfSxcbn0pO1xuIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7QUFxQkEsTUFBTSxPQUFPLFFBQVEsSUFBSSxRQUFRO0FBQ2pDLE1BQU0sT0FBTyxPQUFPLFFBQVEsSUFBSSxRQUFRLEtBQUs7QUFFN0MsTUFBTSxhQUFhO0NBQ2pCLE1BQU0sUUFBUSxJQUFJLGtCQUFrQjtDQUNwQyxNQUFNLE9BQU8sUUFBUSxJQUFJLGtCQUFrQixJQUFJO0NBQy9DLE1BQU0sUUFBUSxJQUFJLGtCQUFrQjtDQUNwQyxVQUFVLFFBQVEsSUFBSSxzQkFBc0I7Q0FDNUMsVUFBVSxRQUFRLElBQUksa0JBQWtCO0FBQzFDO0FBRUEsSUFBQSx3QkFBZSxhQUFhO0NBQzFCLGFBQWEsUUFBUSxJQUFJLGdCQUFnQjtDQUN6QyxVQUFVLEVBQ1IsZ0JBQWdCLEVBQ2QsWUFBWTtFQUNWLEdBQUc7RUFDSCxXQUFXO0VBQ1gsNkJBQTZCO0NBQy9CLEVBQ0YsRUFDRjtDQUNBLEtBQUs7RUFD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
190
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic29uYW11LmNvbmZpZy5qcyIsIm5hbWVzIjpbImNhY2hlRHJpdmVycyJdLCJzb3VyY2VzIjpbIi4uL3NyYy9zb25hbXUuY29uZmlnLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBwYXRoIGZyb20gXCJwYXRoXCI7XG5cbmltcG9ydCB7IGdldENvbnNvbGVTaW5rIH0gZnJvbSBcIkBsb2d0YXBlL2xvZ3RhcGVcIjtcbmltcG9ydCB7IGdldExvZ2dlciB9IGZyb20gXCJAbG9ndGFwZS9sb2d0YXBlXCI7XG5pbXBvcnQgeyBnZXRQcmV0dHlGb3JtYXR0ZXIgfSBmcm9tIFwiQGxvZ3RhcGUvcHJldHR5XCI7XG5pbXBvcnQgeyBDYWNoZVByZXNldHMsIGRlZmluZUNvbmZpZyB9IGZyb20gXCJzb25hbXVcIjtcbmltcG9ydCB7IGRyaXZlcnMgYXMgY2FjaGVEcml2ZXJzLCBzdG9yZSB9IGZyb20gXCJzb25hbXUvY2FjaGVcIjtcblxuaW1wb3J0IHsgbW9uaXRMb2dTaW5rIH0gZnJvbSBcIi4vYXBwbGljYXRpb24vbW9uaXQvbG9nLWJ1ZmZlclwiO1xuaW1wb3J0IHtcbiAgc3RhcnRFeHBpcmVkVG9rZW5SZW1pbmRlcixcbiAgc3RvcEV4cGlyZWRUb2tlblJlbWluZGVyLFxufSBmcm9tIFwiLi9hcHBsaWNhdGlvbi9xZ3JpZC9leHBpcmVkLXRva2VuLXJlbWluZGVyXCI7XG5pbXBvcnQgeyBRZ3JpZERpc3BhdGNoZXIgfSBmcm9tIFwiLi9hcHBsaWNhdGlvbi9xZ3JpZC9xZ3JpZC5kaXNwYXRjaGVyXCI7XG5pbXBvcnQgeyBRZ3JpZEZyYW1lIH0gZnJvbSBcIi4vYXBwbGljYXRpb24vcWdyaWQvcWdyaWQuZnJhbWVcIjtcbmltcG9ydCB7IFRva2VuU3Vic2NyaWJlciB9IGZyb20gXCIuL2FwcGxpY2F0aW9uL3FncmlkL3Rva2VuLXN1YnNjcmliZXJcIjtcbmltcG9ydCB7IGVuc3VyZVRva2Vuc1RyaWdnZXIgfSBmcm9tIFwiLi9hcHBsaWNhdGlvbi9xZ3JpZC90b2tlbi10cmlnZ2VyLXNldHVwXCI7XG5pbXBvcnQgeyBoYW5kbGVTZXJ2ZXJFcnJvciB9IGZyb20gXCIuL3NlcnZlci1lcnJvci1oYW5kbGVyXCI7XG5pbXBvcnQgeyBBbnRocm9waWNEaXNwYXRjaGVyIH0gZnJvbSBcIi4vdXRpbHMvcHJvdmlkZXJzL2FudGhyb3BpYy9hbnRocm9waWMtZGlzcGF0Y2hlclwiO1xuaW1wb3J0IHsgT3BlbkFJRGlzcGF0Y2hlciB9IGZyb20gXCIuL3V0aWxzL3Byb3ZpZGVycy9vcGVuYWkvb3BlbmFpLWRpc3BhdGNoZXJcIjtcblxuY29uc3QgaG9zdCA9IHByb2Nlc3MuZW52LkhPU1QgPz8gXCJsb2NhbGhvc3RcIjtcbmNvbnN0IHBvcnQgPSBOdW1iZXIocHJvY2Vzcy5lbnYuUE9SVCA/PyA0NDkwMCk7XG5cbmNvbnN0IGNvbm5Db25maWcgPSB7XG4gIGhvc3Q6IHByb2Nlc3MuZW52LlNPTkFNVV9EQl9IT1NUID8/IFwibG9jYWxob3N0XCIsXG4gIHBvcnQ6IE51bWJlcihwcm9jZXNzLmVudi5TT05BTVVfREJfUE9SVCA/PyA1NDMyKSxcbiAgdXNlcjogcHJvY2Vzcy5lbnYuU09OQU1VX0RCX1VTRVIgPz8gXCJwb3N0Z3Jlc1wiLFxuICBwYXNzd29yZDogcHJvY2Vzcy5lbnYuU09OQU1VX0RCX1BBU1NXT1JEID8/IFwicG9zdGdyZXNcIixcbiAgZGF0YWJhc2U6IHByb2Nlc3MuZW52LlNPTkFNVV9EQl9OQU1FID8/IFwicWdyaWRcIixcbn07XG5cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHByb2plY3ROYW1lOiBwcm9jZXNzLmVudi5QUk9KRUNUX05BTUUgPz8gXCJRZ3JpZFwiLFxuICBkYXRhYmFzZToge1xuICAgIGRlZmF1bHRPcHRpb25zOiB7XG4gICAgICBjb25uZWN0aW9uOiB7XG4gICAgICAgIC4uLmNvbm5Db25maWcsXG4gICAgICAgIGtlZXBBbGl2ZTogdHJ1ZSxcbiAgICAgICAga2VlcEFsaXZlSW5pdGlhbERlbGF5TWlsbGlzOiAxMDAwMCxcbiAgICAgIH0sXG4gICAgfSxcbiAgfSxcbiAgYXBpOiB7XG4gICAgZGlyOiBcImFwaVwiLFxuICAgIHRpbWV6b25lOiBcIkFzaWEvU2VvdWxcIixcbiAgICByb3V0ZToge1xuICAgICAgcHJlZml4OiBcIi9hcGlcIixcbiAgICB9LFxuICB9LFxuICBpMThuOiB7XG4gICAgZGVmYXVsdExvY2FsZTogXCJrb1wiLFxuICAgIHN1cHBvcnRlZExvY2FsZXM6IFtcImtvXCIsIFwiZW5cIl0sXG4gIH0sXG4gIHN5bmM6IHtcbiAgICB0YXJnZXRzOiBbXCJ3ZWJcIl0sXG4gIH0sXG4gIHRlc3Q6IHtcbiAgICBwYXJhbGxlbDogdHJ1ZSxcbiAgICBtYXhXb3JrZXJzOiA0LFxuICAgIGRldlJ1bm5lcjogeyBlbmFibGVkOiB0cnVlIH0sXG4gIH0sXG4gIGxvZ2dpbmc6IHtcbiAgICBzaW5rczoge1xuICAgICAgY29uc29sZTogZ2V0Q29uc29sZVNpbmsoe1xuICAgICAgICBmb3JtYXR0ZXI6IGdldFByZXR0eUZvcm1hdHRlcih7XG4gICAgICAgICAgdGltZXN0YW1wOiBcInRpbWVcIixcbiAgICAgICAgICBjYXRlZ29yeVdpZHRoOiAyMCxcbiAgICAgICAgICBjYXRlZ29yeVRydW5jYXRlOiBcIm1pZGRsZVwiLFxuICAgICAgICB9KSxcbiAgICAgIH0pLFxuICAgICAgLy8gTW9uaXQg7YOt7JqpIGluLW1lbW9yeSByaW5nIGJ1ZmZlci4g7Y+s66e37J2AIHNpbmsg67OEIOKAlCDsvZjshpQg7Lac66Cl7J2AIOq3uOuMgOuhnOuLpC5cbiAgICAgIG1vbml0OiBtb25pdExvZ1NpbmssXG4gICAgfSxcbiAgICBsb2dnZXJzOiBbXG4gICAgICAvLyBxZ3JpZCDroZzqt7ggKOyalOyyrSDrtoTrsLAsIG9hdXRoL3VzYWdlIEFQSSlcbiAgICAgIHsgY2F0ZWdvcnk6IFtcInFncmlkXCJdLCBzaW5rczogW1wiY29uc29sZVwiLCBcIm1vbml0XCJdLCBsb3dlc3RMZXZlbDogXCJpbmZvXCIgfSxcbiAgICAgIC8vIHNvbmFtdSDroZzqt7jripQgd2FybmluZyDsnbTsg4Hrp4xcbiAgICAgIHsgY2F0ZWdvcnk6IFtcInNvbmFtdVwiXSwgc2lua3M6IFtcImNvbnNvbGVcIiwgXCJtb25pdFwiXSwgbG93ZXN0TGV2ZWw6IFwid2FybmluZ1wiIH0sXG4gICAgICAvLyBmYXN0aWZ5IOuhnOq3uCDssKjri6hcbiAgICAgIHsgY2F0ZWdvcnk6IFtcImZhc3RpZnlcIl0sIHNpbmtzOiBbXSwgbG93ZXN0TGV2ZWw6IFwiZmF0YWxcIiB9LFxuICAgIF0sXG4gIH0sXG4gIHNlcnZlcjoge1xuICAgIGxpc3RlbjogeyBwb3J0LCBob3N0IH0sXG4gICAgZmFzdGlmeToge1xuICAgICAgYm9keUxpbWl0OiAxMCAqIDEwMjQgKiAxMDI0LFxuICAgIH0sXG4gICAgcGx1Z2luczoge1xuICAgICAgZm9ybWJvZHk6IHRydWUsXG4gICAgICBxczogdHJ1ZSxcbiAgICAgIHNzZTogdHJ1ZSxcbiAgICAgIG11bHRpcGFydDogeyBsaW1pdHM6IHsgZmlsZVNpemU6IDEwMjQgKiAxMDI0ICogMzAgfSB9LFxuICAgICAgc3RhdGljOiB7XG4gICAgICAgIHJvb3Q6IHBhdGguam9pbihpbXBvcnQubWV0YS5kaXJuYW1lLCBcIi8uLi9cIiwgXCJwdWJsaWNcIiksXG4gICAgICAgIHByZWZpeDogXCIvYXBpL3B1YmxpY1wiLFxuICAgICAgfSxcbiAgICAgIGN1c3RvbTogKHNlcnZlcikgPT4ge1xuICAgICAgICAvLyBPQXV0aCDsvZzrsLEg4oCUIEFudGhyb3BpY+ydtCAvY2FsbGJhY2vsnLzroZwg66as64uk7J2066CJ7Yq4XG4gICAgICAgIHNlcnZlci5nZXQoXCIvY2FsbGJhY2tcIiwgYXN5bmMgKHJlcXVlc3QsIHJlcGx5KSA9PiB7XG4gICAgICAgICAgY29uc3QgeyBjb2RlLCBzdGF0ZSB9ID0gcmVxdWVzdC5xdWVyeSBhcyB7IGNvZGU/OiBzdHJpbmc7IHN0YXRlPzogc3RyaW5nIH07XG4gICAgICAgICAgaWYgKCFjb2RlIHx8ICFzdGF0ZSkge1xuICAgICAgICAgICAgcmV0dXJuIHJlcGx5LnJlZGlyZWN0KFwiLz9vYXV0aD1lcnJvciZyZWFzb249bWlzc2luZ19wYXJhbXNcIik7XG4gICAgICAgICAgfVxuICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICByZXR1cm4gUWdyaWRGcmFtZS5oYW5kbGVPQXV0aENhbGxiYWNrKGNvZGUsIHN0YXRlLCByZXBseSk7XG4gICAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgcmV0dXJuIHJlcGx5LnJlZGlyZWN0KFxuICAgICAgICAgICAgICBgLz9vYXV0aD1lcnJvciZyZWFzb249JHtlbmNvZGVVUklDb21wb25lbnQoKGUgYXMgRXJyb3IpLm1lc3NhZ2UpfWAsXG4gICAgICAgICAgICApO1xuICAgICAgICAgIH1cbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gT3BlbkFJIE9BdXRoIOuKlCBjb2RleCBhcHAtc2VydmVyIOqwgCDsnpDssrQgY2FsbGJhY2sg7ISc67KE66W8IOyYrOumvFxuICAgICAgICAvLyBoYW5kbGVPcGVuQUlDYWxsYmFjayDrtojtlYTsmpQg4oCUIG9hdXRoQ29tcGxldGVPcGVuQUkgQVBJIOuhnCDsmYTro4wg7ZmV7J24XG4gICAgICB9LFxuICAgIH0sXG4gICAgYXBpQ29uZmlnOiB7XG4gICAgICBjb250ZXh0UHJvdmlkZXI6IChkZWZhdWx0Q29udGV4dCwgcmVxdWVzdCkgPT4ge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIC4uLmRlZmF1bHRDb250ZXh0LFxuICAgICAgICAgIGlwOiByZXF1ZXN0LmlwLFxuICAgICAgICAgIGJvZHk6IHJlcXVlc3QuYm9keSxcbiAgICAgICAgfTtcbiAgICAgIH0sXG4gICAgICBndWFyZEhhbmRsZXI6ICgpID0+IHt9LFxuICAgICAgY2FjaGVDb250cm9sSGFuZGxlcjogKHJlcSkgPT4ge1xuICAgICAgICBzd2l0Y2ggKHJlcS50eXBlKSB7XG4gICAgICAgICAgY2FzZSBcImFzc2V0c1wiOlxuICAgICAgICAgICAgLy8gSGFzaCDtj6ztlajrkJwg7YyM7J28OiDsmIHqtawg7LqQ7IucXG4gICAgICAgICAgICBpZiAocmVxLnBhdGgubWF0Y2goLy1bYS1mMC05XStcXC4vKSkge1xuICAgICAgICAgICAgICByZXR1cm4gQ2FjaGVQcmVzZXRzLmltbXV0YWJsZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiBDYWNoZVByZXNldHMubG9uZ0xpdmVkO1xuXG4gICAgICAgICAgY2FzZSBcImFwaVwiOlxuICAgICAgICAgICAgLy8gR0VUIOyalOyyreunjCDsupDsi7Eg6rOg66CkXG4gICAgICAgICAgICBpZiAocmVxLm1ldGhvZCA9PT0gXCJHRVRcIikge1xuICAgICAgICAgICAgICAvLyDtirnsoJUg6rK966Gc64qUIOynp+ydgCDsupDsi5xcbiAgICAgICAgICAgICAgaWYgKHJlcS5wYXRoLnN0YXJ0c1dpdGgoXCIvYXBpL3N0YXRpYy1kYXRhXCIpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIENhY2hlUHJlc2V0cy5zaG9ydExpdmVkO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGlmIChyZXEucGF0aC5zdGFydHNXaXRoKFwiL2FwaS90ZXJtc1wiKSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBDYWNoZVByZXNldHMubWVkaXVtTGl2ZWQ7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIOq4sOuzuDog7LqQ7IucIOyXhuydjFxuICAgICAgICAgICAgcmV0dXJuIENhY2hlUHJlc2V0cy5ub0NhY2hlO1xuXG4gICAgICAgICAgY2FzZSBcInNzclwiOlxuICAgICAgICAgICAgLy8gU1NSIO2OmOydtOyngDogMTDstIgg7LqQ7IucXG4gICAgICAgICAgICByZXR1cm4gQ2FjaGVQcmVzZXRzLnNzcjtcblxuICAgICAgICAgIGNhc2UgXCJjc3JcIjpcbiAgICAgICAgICAgIC8vIENTUiBmYWxsYmFjayAoaW5kZXguaHRtbCk6IDHrtoQg7LqQ7IucXG4gICAgICAgICAgICByZXR1cm4gQ2FjaGVQcmVzZXRzLnNob3J0TGl2ZWQ7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIENhY2hlUHJlc2V0cy5ub0NhY2hlO1xuICAgICAgfSxcbiAgICB9LFxuICAgIGNhY2hlOiB7XG4gICAgICBkZWZhdWx0OiBcIm1haW5cIixcbiAgICAgIHN0b3Jlczoge1xuICAgICAgICBtYWluOiBzdG9yZSgpLnVzZUwxTGF5ZXIoY2FjaGVEcml2ZXJzLm1lbW9yeSh7IG1heFNpemU6IFwiNTBtYlwiIH0pKSxcbiAgICAgIH0sXG4gICAgICB0dGw6IFwiNW1cIixcbiAgICAgIHByZWZpeDogXCJcIixcbiAgICB9LFxuICAgIGxpZmVjeWNsZToge1xuICAgICAgb25TdGFydDogYXN5bmMgKCkgPT4ge1xuICAgICAgICBjb25zdCBsb2cgPSBnZXRMb2dnZXIoW1wicWdyaWRcIiwgXCJzdGFydHVwXCJdKTtcblxuICAgICAgICBsZXQgdHJpZ2dlclJlYWR5ID0gdHJ1ZTtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBhd2FpdCBlbnN1cmVUb2tlbnNUcmlnZ2VyKGNvbm5Db25maWcpO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgdHJpZ2dlclJlYWR5ID0gZmFsc2U7XG4gICAgICAgICAgbG9nLndhcm4oYHRyaWdnZXIgc2V0dXAgZmFpbGVkOiAkeyhlIGFzIEVycm9yKS5tZXNzYWdlfWApO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3Qgc3Vic2NyaWJlciA9IG5ldyBUb2tlblN1YnNjcmliZXIoY29ubkNvbmZpZywgUWdyaWREaXNwYXRjaGVyKTtcbiAgICAgICAgUWdyaWREaXNwYXRjaGVyLnN1YnNjcmliZXIgPSBzdWJzY3JpYmVyO1xuXG4gICAgICAgIGNvbnN0IHN0YXJ0ZWQgPSBhd2FpdCBzdWJzY3JpYmVyLnN0YXJ0KCk7XG5cbiAgICAgICAgLy8g6riw64+ZIOyDge2DnOulvCDrgqjquLTri6Qg4oCUIEhUVFAg64qUIOydtOuvuCDsl7TroKQg7J6I7Ja07IScLCDspIDruYQg7KCEIOyalOyyreydtCBcIuyeoOyLnCDtm4Qg65CY64qU7KeAXCJcbiAgICAgICAgLy8gXCLsnqzsi5zrj4TtlbTrj4Qg7JWIIOuQmOuKlOyngFwi66W8IOq1rOu2hO2VtCDsnZHri7XtlbTslbwg7ZWc64ukKDUwMyB2cyA1MDApLlxuICAgICAgICB0cnkge1xuICAgICAgICAgIGNvbnN0IG9wZW5haURpc3BhdGNoZXIgPSBuZXcgT3BlbkFJRGlzcGF0Y2hlcigpO1xuICAgICAgICAgIGF3YWl0IG9wZW5haURpc3BhdGNoZXIuc3RhcnQoKTtcbiAgICAgICAgICBRZ3JpZERpc3BhdGNoZXIub3BlbmFpRGlzcGF0Y2hlciA9IG9wZW5haURpc3BhdGNoZXI7XG4gICAgICAgICAgUWdyaWREaXNwYXRjaGVyLnN0YXJ0dXBTdGF0ZS5vcGVuYWkgPSBcInJlYWR5XCI7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICBRZ3JpZERpc3BhdGNoZXIuc3RhcnR1cFN0YXRlLm9wZW5haSA9IFwiZmFpbGVkXCI7XG4gICAgICAgICAgbG9nLndhcm4oYG9wZW5haSBkaXNwYXRjaGVyIGZhaWxlZDogJHsoZSBhcyBFcnJvcikubWVzc2FnZX1gKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgY29uc3QgYW50aHJvcGljRGlzcGF0Y2hlciA9IG5ldyBBbnRocm9waWNEaXNwYXRjaGVyKCk7XG4gICAgICAgICAgYXdhaXQgYW50aHJvcGljRGlzcGF0Y2hlci5zdGFydCgpO1xuICAgICAgICAgIFFncmlkRGlzcGF0Y2hlci5hbnRocm9waWNEaXNwYXRjaGVyID0gYW50aHJvcGljRGlzcGF0Y2hlcjtcbiAgICAgICAgICBRZ3JpZERpc3BhdGNoZXIuc3RhcnR1cFN0YXRlLmFudGhyb3BpYyA9IFwicmVhZHlcIjtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgIFFncmlkRGlzcGF0Y2hlci5zdGFydHVwU3RhdGUuYW50aHJvcGljID0gXCJmYWlsZWRcIjtcbiAgICAgICAgICBsb2cud2FybihgYW50aHJvcGljIGRpc3BhdGNoZXIgZmFpbGVkOiAkeyhlIGFzIEVycm9yKS5tZXNzYWdlfWApO1xuICAgICAgICB9XG5cbiAgICAgICAgc3RhcnRFeHBpcmVkVG9rZW5SZW1pbmRlcigpO1xuXG4gICAgICAgIGNvbnN0IGFudGhyb3BpY0NvdW50ID0gUWdyaWREaXNwYXRjaGVyLmFudGhyb3BpY0Rpc3BhdGNoZXI/LnRva2VuQ291bnQgPz8gMDtcbiAgICAgICAgY29uc3Qgb3BlbmFpUmVhZHkgPSBRZ3JpZERpc3BhdGNoZXIub3BlbmFpRGlzcGF0Y2hlcj8ucmVhZHlXb3JrZXJDb3VudCA/PyAwO1xuICAgICAgICBjb25zdCBvcGVuYWlUb3RhbCA9IFFncmlkRGlzcGF0Y2hlci5vcGVuYWlEaXNwYXRjaGVyPy53b3JrZXJDb3VudCA/PyAwO1xuXG4gICAgICAgIGxvZy5pbmZvKGBsaXN0ZW5pbmcgb24gaHR0cDovLyR7aG9zdH06JHtwb3J0fWApO1xuICAgICAgICBsb2cuaW5mbyhgYW50aHJvcGljOiAke2FudGhyb3BpY0NvdW50fSB0b2tlbnMgcmVhZHlgKTtcbiAgICAgICAgbG9nLmluZm8oYG9wZW5haTogJHtvcGVuYWlSZWFkeX0vJHtvcGVuYWlUb3RhbH0gdG9rZW5zIHJlYWR5YCk7XG4gICAgICAgIGxvZy5pbmZvKFxuICAgICAgICAgIGBzdWJzY3JpYmVyOiAke3N0YXJ0ZWQgPyBcIkxJU1RFTiBhY3RpdmVcIiA6IFwiZGVncmFkZWRcIn0ke3RyaWdnZXJSZWFkeSA/IFwiXCIgOiBcIiwgdHJpZ2dlciBmYWlsZWRcIn1gLFxuICAgICAgICApO1xuICAgICAgfSxcbiAgICAgIG9uU2h1dGRvd246IGFzeW5jICgpID0+IHtcbiAgICAgICAgY29uc3QgbG9nID0gZ2V0TG9nZ2VyKFtcInFncmlkXCIsIFwic3RhcnR1cFwiXSk7XG4gICAgICAgIHN0b3BFeHBpcmVkVG9rZW5SZW1pbmRlcigpO1xuICAgICAgICBpZiAoUWdyaWREaXNwYXRjaGVyLm9wZW5haURpc3BhdGNoZXIpIHtcbiAgICAgICAgICBhd2FpdCBRZ3JpZERpc3BhdGNoZXIub3BlbmFpRGlzcGF0Y2hlci5zdG9wKCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKFFncmlkRGlzcGF0Y2hlci5hbnRocm9waWNEaXNwYXRjaGVyKSB7XG4gICAgICAgICAgYXdhaXQgUWdyaWREaXNwYXRjaGVyLmFudGhyb3BpY0Rpc3BhdGNoZXIuc3RvcCgpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChRZ3JpZERpc3BhdGNoZXIuc3Vic2NyaWJlcikge1xuICAgICAgICAgIGF3YWl0IFFncmlkRGlzcGF0Y2hlci5zdWJzY3JpYmVyLnN0b3AoKTtcbiAgICAgICAgfVxuICAgICAgICBsb2cuaW5mbyhcImdyYWNlZnVsIHNodXRkb3duXCIpO1xuICAgICAgfSxcbiAgICAgIG9uRXJyb3I6IChlcnJvciwgX3JlcXVlc3QsIHJlcGx5KSA9PiB7XG4gICAgICAgIGhhbmRsZVNlcnZlckVycm9yKGVycm9yLCByZXBseSk7XG4gICAgICB9LFxuICAgIH0sXG4gIH0sXG59KTtcbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7O0FBcUJBLE1BQU0sT0FBTyxRQUFRLElBQUksUUFBUTtBQUNqQyxNQUFNLE9BQU8sT0FBTyxRQUFRLElBQUksUUFBUSxLQUFLO0FBRTdDLE1BQU0sYUFBYTtDQUNqQixNQUFNLFFBQVEsSUFBSSxrQkFBa0I7Q0FDcEMsTUFBTSxPQUFPLFFBQVEsSUFBSSxrQkFBa0IsSUFBSTtDQUMvQyxNQUFNLFFBQVEsSUFBSSxrQkFBa0I7Q0FDcEMsVUFBVSxRQUFRLElBQUksc0JBQXNCO0NBQzVDLFVBQVUsUUFBUSxJQUFJLGtCQUFrQjtBQUMxQztBQUVBLElBQUEsd0JBQWUsYUFBYTtDQUMxQixhQUFhLFFBQVEsSUFBSSxnQkFBZ0I7Q0FDekMsVUFBVSxFQUNSLGdCQUFnQixFQUNkLFlBQVk7RUFDVixHQUFHO0VBQ0gsV0FBVztFQUNYLDZCQUE2QjtDQUMvQixFQUNGLEVBQ0Y7Q0FD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
@@ -0,0 +1,53 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { buildReminderContext } from "./expired-token-reminder";
4
+
5
+ const userMap = new Map([
6
+ ["yds", "U09NAJUQSFQ"],
7
+ ["haze", "U09N96NHZB7"],
8
+ ]);
9
+
10
+ describe("buildReminderContext", () => {
11
+ it("매핑된 토큰은 멘션으로 개인에게 꽂는다", () => {
12
+ const text = buildReminderContext([{ name: "anthropic/yds", provider: "anthropic" }], userMap);
13
+
14
+ expect(text).toBe("<@U09NAJUQSFQ> anthropic/yds");
15
+ });
16
+
17
+ it("공용 계정처럼 매핑이 없으면 멘션 없이 이름만 남긴다", () => {
18
+ const text = buildReminderContext(
19
+ [{ name: "anthropic/dev-common", provider: "anthropic" }],
20
+ userMap,
21
+ );
22
+
23
+ expect(text).toBe("anthropic/dev-common");
24
+ });
25
+
26
+ it("같은 사람의 provider 별 토큰을 각각 구분해 보여준다", () => {
27
+ const text = buildReminderContext(
28
+ [
29
+ { name: "anthropic/yds", provider: "anthropic" },
30
+ { name: "openai/yds", provider: "openai" },
31
+ ],
32
+ userMap,
33
+ );
34
+
35
+ // 멘션만 두 번 나오면 어느 토큰인지 알 수 없다 — 토큰명이 함께 있어야 한다.
36
+ expect(text.split("\n")).toEqual([
37
+ "<@U09NAJUQSFQ> anthropic/yds",
38
+ "<@U09NAJUQSFQ> openai/yds",
39
+ ]);
40
+ });
41
+
42
+ it("provider prefix 없이 저장된 이름도 매핑한다", () => {
43
+ const text = buildReminderContext([{ name: "haze", provider: "anthropic" }], userMap);
44
+
45
+ expect(text).toBe("<@U09N96NHZB7> haze");
46
+ });
47
+
48
+ it("이름이 없는 토큰도 줄을 잃지 않는다", () => {
49
+ const text = buildReminderContext([{ name: null, provider: "openai" }], userMap);
50
+
51
+ expect(text).toBe("unnamed");
52
+ });
53
+ });
@@ -0,0 +1,74 @@
1
+ /**
2
+ * 세션 만료 알림 반복.
3
+ *
4
+ * 만료 순간의 알림(`token-death.ts`)은 그때 채널을 보지 않으면 놓친다. 재로그인은 사람이
5
+ * 해야 하는 일이라 놓치면 토큰이 계속 빠진 채로 남는다. 그래서 아직 재로그인되지 않은
6
+ * 토큰을 같은 문구로 다시 알린다 — 사건이 하나이므로 알림도 한 종류다.
7
+ *
8
+ * 여러 토큰이 만료돼 있으면 한 메시지에 모은다. 토큰마다 메시지를 보내면 채널이 시끄러워
9
+ * 정작 읽히지 않는다.
10
+ *
11
+ * 비활성 토큰이 없으면 보내지 않는다 — "이상 없음"을 주기적으로 알리면 채널이 무뎌진다.
12
+ */
13
+ import { getLogger } from "@logtape/logtape";
14
+
15
+ import { notifySlack, SLACK_COLOR } from "../../utils/slack-notify";
16
+ import { TokenModel } from "../token/token.model";
17
+ import { getSlackUserMap, mentionFor } from "./slack-user-map";
18
+
19
+ const logger = getLogger(["qgrid", "expired-reminder"]);
20
+
21
+ const MINUTE_MS = 60_000;
22
+
23
+ export function buildReminderContext(
24
+ tokens: { name: string | null; provider: string }[],
25
+ userMap: Map<string, string>,
26
+ ): string {
27
+ return tokens
28
+ .map((token) => {
29
+ const mention = mentionFor(token.name, userMap);
30
+ const label = token.name ?? "unnamed";
31
+ // 멘션이 있어도 어떤 토큰인지는 남긴다 — 한 사람이 provider 별로 여러 개를 가진다.
32
+ return mention ? `${mention} ${label}` : label;
33
+ })
34
+ .join("\n");
35
+ }
36
+
37
+ async function sendReminder(userMap: Map<string, string>): Promise<void> {
38
+ const inactive = await TokenModel.findInactive("A");
39
+ if (inactive.length === 0) return;
40
+
41
+ await notifySlack({
42
+ // 만료 순간 알림과 같은 제목을 쓴다. 같은 사건이므로 다른 이름을 붙이면 별개 문제로 읽힌다.
43
+ title: "세션 만료",
44
+ subject: inactive.length > 1 ? `${inactive.length}건` : (inactive[0]!.name ?? "unnamed"),
45
+ context: `${buildReminderContext(inactive, userMap)}\n재로그인이 필요합니다`,
46
+ color: SLACK_COLOR.bad,
47
+ });
48
+ }
49
+
50
+ let timer: ReturnType<typeof setInterval> | null = null;
51
+
52
+ /**
53
+ * `SLACK_EXPIRY_REMINDER_INTERVAL_MINUTES` 가 양수일 때만 켜진다. 미설정·0 이면 비활성.
54
+ *
55
+ * 인스턴스가 여러 개면 각자 보낸다. 만료 순간 알림과 달리 상태 변화가 없어 DB 로 억제할 수
56
+ * 없고, 하루 몇 건 수준이라 중복을 감수하는 편이 잠금 장치를 두는 것보다 낫다.
57
+ */
58
+ export function startExpiredTokenReminder(): void {
59
+ const minutes = Number(process.env.SLACK_EXPIRY_REMINDER_INTERVAL_MINUTES ?? 0);
60
+ if (!Number.isFinite(minutes) || minutes <= 0) return;
61
+
62
+ const userMap = getSlackUserMap();
63
+ logger.info(`expired token reminder every ${minutes}m (${userMap.size} user mappings)`);
64
+
65
+ timer = setInterval(() => {
66
+ sendReminder(userMap).catch((e) => logger.warn(`reminder failed: ${(e as Error).message}`));
67
+ }, minutes * MINUTE_MS);
68
+ }
69
+
70
+ export function stopExpiredTokenReminder(): void {
71
+ if (!timer) return;
72
+ clearInterval(timer);
73
+ timer = null;
74
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 토큰 소유자 → Slack 사용자 멘션.
3
+ *
4
+ * Slack 표시 이름과 토큰명이 자주 어긋나(haze→haze.lee, byeongjun→potados) `users.list`
5
+ * 자동 매칭은 조용히 실패한다. 멘션이 빠지면 알림이 개인에게 닿지 않아 목적 자체가
6
+ * 사라지므로 `SLACK_USER_MAP` 명시 매핑만 쓴다.
7
+ *
8
+ * 공용 계정(bysuco, dev-common 등)은 매핑에 넣지 않는다 — 멘션 없이 이름만 나가는 것이
9
+ * 맞는 동작이다.
10
+ */
11
+
12
+ /** `토큰명:SlackUserId` 목록을 파싱한다. */
13
+ export function parseSlackUserMap(raw: string | undefined): Map<string, string> {
14
+ const map = new Map<string, string>();
15
+ if (!raw) return map;
16
+
17
+ for (const entry of raw.split(",")) {
18
+ const [name, userId] = entry.split(":").map((s) => s.trim());
19
+ if (name && userId) map.set(name, userId);
20
+ }
21
+ return map;
22
+ }
23
+
24
+ /** `anthropic/yds` → `yds`. provider prefix 없이 저장된 이름도 그대로 받는다. */
25
+ export function ownerOf(tokenName: string | null): string {
26
+ if (!tokenName) return "";
27
+ const slash = tokenName.indexOf("/");
28
+ return slash === -1 ? tokenName : tokenName.slice(slash + 1);
29
+ }
30
+
31
+ /** 매핑이 있으면 `<@U...>`, 없으면 빈 문자열. 호출부가 멘션 유무로 문구를 고른다. */
32
+ export function mentionFor(tokenName: string | null, userMap: Map<string, string>): string {
33
+ const userId = userMap.get(ownerOf(tokenName));
34
+ return userId ? `<@${userId}>` : "";
35
+ }
36
+
37
+ /**
38
+ * 환경변수는 프로세스 수명 동안 고정이라 매번 파싱할 이유가 없다. 사망 알림은 요청 경로에서
39
+ * 호출되므로 캐시해 둔다.
40
+ */
41
+ let cached: Map<string, string> | null = null;
42
+
43
+ export function getSlackUserMap(): Map<string, string> {
44
+ cached ??= parseSlackUserMap(process.env.SLACK_USER_MAP);
45
+ return cached;
46
+ }
47
+
48
+ /** 테스트에서 env 를 바꿔 끼울 때 쓴다. */
49
+ export function resetSlackUserMapCache(): void {
50
+ cached = null;
51
+ }
@@ -10,6 +10,7 @@ import { getLogger } from "@logtape/logtape";
10
10
  import { getRefreshToken } from "../../utils/providers/common/credentials";
11
11
  import { notifySlack, SLACK_COLOR } from "../../utils/slack-notify";
12
12
  import { TokenModel } from "../token/token.model";
13
+ import { getSlackUserMap, mentionFor } from "./slack-user-map";
13
14
 
14
15
  const logger = getLogger(["qgrid", "token-death"]);
15
16
 
@@ -49,10 +50,15 @@ export async function deactivateAuthDeadToken(
49
50
  }
50
51
 
51
52
  logger.warn(`token deactivated (auth dead): ${token.name}(id=${token.id}) reason=${reasonCode}`);
53
+ // 만료 직후가 재로그인하기 가장 좋은 타이밍이다. 소유자를 멘션해 그 순간 당사자에게
54
+ // 꽂는다 — 다이제스트를 기다리면 다음 주기까지 토큰이 빠진 채로 남는다.
55
+ const mention = mentionFor(token.name, getSlackUserMap());
52
56
  void notifySlack({
53
57
  title: "세션 만료",
54
58
  subject: token.name,
55
- context: `${token.provider} · ${reasonCode} · 재로그인이 필요합니다`,
59
+ context: [mention, token.provider, reasonCode, "재로그인이 필요합니다"]
60
+ .filter(Boolean)
61
+ .join(" · "),
56
62
  color: SLACK_COLOR.bad,
57
63
  });
58
64
  return true;
@@ -74,11 +80,13 @@ function notifyLastActiveTokenDying(token: DeadTokenRef, reasonCode: string): vo
74
80
  if (previous !== undefined && now - previous < LAST_ACTIVE_ALERT_COOLDOWN_MS) return;
75
81
  lastActiveAlertAt.set(token.provider, now);
76
82
 
83
+ const mention = mentionFor(token.name, getSlackUserMap());
77
84
  void notifySlack({
78
85
  title: "마지막 토큰 사망",
79
86
  subject: token.name,
80
87
  context:
81
- `${token.provider} · ${reasonCode} · 풀이 비지 않도록 유지 중입니다. ` +
88
+ [mention, token.provider, reasonCode].filter(Boolean).join(" · ") +
89
+ ` · 풀이 비지 않도록 유지 중입니다. ` +
82
90
  `전 토큰이 동시에 실패했다면 client_id 취소나 OAuth 계약 변경을 의심하세요`,
83
91
  color: SLACK_COLOR.bad,
84
92
  });
@@ -87,10 +95,11 @@ function notifyLastActiveTokenDying(token: DeadTokenRef, reasonCode: string): vo
87
95
  /** 로그인이 완료돼 토큰이 저장됐을 때. 신규·재로그인을 가리지 않는다. */
88
96
  export function notifyTokenAdded(name: string, provider: string): void {
89
97
  logger.info(`token added: ${name} (${provider})`);
98
+ const mention = mentionFor(name, getSlackUserMap());
90
99
  void notifySlack({
91
100
  title: "토큰 추가",
92
101
  subject: name,
93
- context: `${provider} · 요청 처리에 사용됩니다`,
102
+ context: [mention, provider, "요청 처리에 사용됩니다"].filter(Boolean).join(" · "),
94
103
  color: SLACK_COLOR.good,
95
104
  });
96
105
  }
@@ -8,9 +8,9 @@ import { drivers as cacheDrivers, store } from "sonamu/cache";
8
8
 
9
9
  import { monitLogSink } from "./application/monit/log-buffer";
10
10
  import {
11
- startExpiredTokenDigest,
12
- stopExpiredTokenDigest,
13
- } from "./application/qgrid/expired-token-digest";
11
+ startExpiredTokenReminder,
12
+ stopExpiredTokenReminder,
13
+ } from "./application/qgrid/expired-token-reminder";
14
14
  import { QgridDispatcher } from "./application/qgrid/qgrid.dispatcher";
15
15
  import { QgridFrame } from "./application/qgrid/qgrid.frame";
16
16
  import { TokenSubscriber } from "./application/qgrid/token-subscriber";
@@ -205,7 +205,7 @@ export default defineConfig({
205
205
  log.warn(`anthropic dispatcher failed: ${(e as Error).message}`);
206
206
  }
207
207
 
208
- startExpiredTokenDigest();
208
+ startExpiredTokenReminder();
209
209
 
210
210
  const anthropicCount = QgridDispatcher.anthropicDispatcher?.tokenCount ?? 0;
211
211
  const openaiReady = QgridDispatcher.openaiDispatcher?.readyWorkerCount ?? 0;
@@ -220,7 +220,7 @@ export default defineConfig({
220
220
  },
221
221
  onShutdown: async () => {
222
222
  const log = getLogger(["qgrid", "startup"]);
223
- stopExpiredTokenDigest();
223
+ stopExpiredTokenReminder();
224
224
  if (QgridDispatcher.openaiDispatcher) {
225
225
  await QgridDispatcher.openaiDispatcher.stop();
226
226
  }