@juspay/shooter 1.24.1 → 1.25.0

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 (165) hide show
  1. package/.claude/hooks/notifier.cjs +32 -4
  2. package/bin/lib/service-manager.cjs +148 -0
  3. package/bin/shooter.cjs +151 -62
  4. package/build/client/_app/immutable/assets/{4.D4EDSN4H.css → 4.ChO_hlLs.css} +1 -1
  5. package/build/client/_app/immutable/assets/4.ChO_hlLs.css.br +0 -0
  6. package/build/client/_app/immutable/assets/4.ChO_hlLs.css.gz +0 -0
  7. package/build/client/_app/immutable/chunks/{DjWRwZyr.js → BstJSK2K.js} +1 -1
  8. package/build/client/_app/immutable/chunks/BstJSK2K.js.br +0 -0
  9. package/build/client/_app/immutable/chunks/BstJSK2K.js.gz +0 -0
  10. package/build/client/_app/immutable/chunks/{CfzjLyJm.js → DINqYbXU.js} +1 -1
  11. package/build/client/_app/immutable/chunks/DINqYbXU.js.br +0 -0
  12. package/build/client/_app/immutable/chunks/DINqYbXU.js.gz +0 -0
  13. package/build/client/_app/immutable/chunks/ssAhjWfF.js +3 -0
  14. package/build/client/_app/immutable/chunks/ssAhjWfF.js.br +0 -0
  15. package/build/client/_app/immutable/chunks/ssAhjWfF.js.gz +0 -0
  16. package/build/client/_app/immutable/entry/{app.9F0rhLIY.js → app.BPK9s2o5.js} +2 -2
  17. package/build/client/_app/immutable/entry/app.BPK9s2o5.js.br +0 -0
  18. package/build/client/_app/immutable/entry/app.BPK9s2o5.js.gz +0 -0
  19. package/build/client/_app/immutable/entry/start.DFPHuGE3.js +1 -0
  20. package/build/client/_app/immutable/entry/start.DFPHuGE3.js.br +2 -0
  21. package/build/client/_app/immutable/entry/start.DFPHuGE3.js.gz +0 -0
  22. package/build/client/_app/immutable/nodes/{0.MlMcxYLT.js → 0.BEbzRcwm.js} +1 -1
  23. package/build/client/_app/immutable/nodes/0.BEbzRcwm.js.br +0 -0
  24. package/build/client/_app/immutable/nodes/0.BEbzRcwm.js.gz +0 -0
  25. package/build/client/_app/immutable/nodes/{1._Bn-HQ9b.js → 1.C_V0SOr9.js} +1 -1
  26. package/build/client/_app/immutable/nodes/1.C_V0SOr9.js.br +0 -0
  27. package/build/client/_app/immutable/nodes/1.C_V0SOr9.js.gz +0 -0
  28. package/build/client/_app/immutable/nodes/{10.CsX0V4R9.js → 10.6V-37_Rl.js} +1 -1
  29. package/build/client/_app/immutable/nodes/10.6V-37_Rl.js.br +0 -0
  30. package/build/client/_app/immutable/nodes/10.6V-37_Rl.js.gz +0 -0
  31. package/build/client/_app/immutable/nodes/{11.lM_b6yUv.js → 11.Lzf-KC6L.js} +1 -1
  32. package/build/client/_app/immutable/nodes/11.Lzf-KC6L.js.br +0 -0
  33. package/build/client/_app/immutable/nodes/11.Lzf-KC6L.js.gz +0 -0
  34. package/build/client/_app/immutable/nodes/{2.sAnHf5go.js → 2.Cl2dMP2L.js} +1 -1
  35. package/build/client/_app/immutable/nodes/2.Cl2dMP2L.js.br +0 -0
  36. package/build/client/_app/immutable/nodes/2.Cl2dMP2L.js.gz +0 -0
  37. package/build/client/_app/immutable/nodes/{3.DEHWta3G.js → 3.CuX2eSna.js} +1 -1
  38. package/build/client/_app/immutable/nodes/3.CuX2eSna.js.br +0 -0
  39. package/build/client/_app/immutable/nodes/3.CuX2eSna.js.gz +0 -0
  40. package/build/client/_app/immutable/nodes/4.2DvqoOaB.js +17 -0
  41. package/build/client/_app/immutable/nodes/4.2DvqoOaB.js.br +0 -0
  42. package/build/client/_app/immutable/nodes/4.2DvqoOaB.js.gz +0 -0
  43. package/build/client/_app/immutable/nodes/{6.-gDjaLSz.js → 6.C7e6zQyP.js} +1 -1
  44. package/build/client/_app/immutable/nodes/6.C7e6zQyP.js.br +0 -0
  45. package/build/client/_app/immutable/nodes/6.C7e6zQyP.js.gz +0 -0
  46. package/build/client/_app/immutable/nodes/{7.R-emDnvX.js → 7.1ygvNTnO.js} +1 -1
  47. package/build/client/_app/immutable/nodes/7.1ygvNTnO.js.br +0 -0
  48. package/build/client/_app/immutable/nodes/7.1ygvNTnO.js.gz +0 -0
  49. package/build/client/_app/immutable/nodes/{8.DXNcFetv.js → 8.CBVmgOk0.js} +1 -1
  50. package/build/client/_app/immutable/nodes/8.CBVmgOk0.js.br +0 -0
  51. package/build/client/_app/immutable/nodes/8.CBVmgOk0.js.gz +0 -0
  52. package/build/client/_app/immutable/nodes/{9.Brv6N-Ji.js → 9.B-_ZFZhj.js} +1 -1
  53. package/build/client/_app/immutable/nodes/9.B-_ZFZhj.js.br +0 -0
  54. package/build/client/_app/immutable/nodes/9.B-_ZFZhj.js.gz +0 -0
  55. package/build/client/_app/version.json +1 -1
  56. package/build/client/_app/version.json.br +0 -0
  57. package/build/client/_app/version.json.gz +0 -0
  58. package/build/server/chunks/{0-CezVlDLP.js → 0-BHU07xt9.js} +2 -2
  59. package/build/server/chunks/{0-CezVlDLP.js.map → 0-BHU07xt9.js.map} +1 -1
  60. package/build/server/chunks/{1-CAwGzW00.js → 1-OVOd8GUH.js} +2 -2
  61. package/build/server/chunks/{1-CAwGzW00.js.map → 1-OVOd8GUH.js.map} +1 -1
  62. package/build/server/chunks/{10-pqorW2OP.js → 10-CRHtvb_u.js} +2 -2
  63. package/build/server/chunks/{10-pqorW2OP.js.map → 10-CRHtvb_u.js.map} +1 -1
  64. package/build/server/chunks/{11-Byxg_lSY.js → 11-CdSex9j1.js} +2 -2
  65. package/build/server/chunks/{11-Byxg_lSY.js.map → 11-CdSex9j1.js.map} +1 -1
  66. package/build/server/chunks/{2-Bbck5mN2.js → 2-bb78aIZ6.js} +2 -2
  67. package/build/server/chunks/{2-Bbck5mN2.js.map → 2-bb78aIZ6.js.map} +1 -1
  68. package/build/server/chunks/{3-BBFbA1P8.js → 3-CsTC6Lrn.js} +2 -2
  69. package/build/server/chunks/{3-BBFbA1P8.js.map → 3-CsTC6Lrn.js.map} +1 -1
  70. package/build/server/chunks/{4-w2W_T8ax.js → 4-DTTu8_Gr.js} +4 -4
  71. package/build/server/chunks/{4-w2W_T8ax.js.map → 4-DTTu8_Gr.js.map} +1 -1
  72. package/build/server/chunks/{6-BI3p-t3i.js → 6-BUgQGB_4.js} +2 -2
  73. package/build/server/chunks/{6-BI3p-t3i.js.map → 6-BUgQGB_4.js.map} +1 -1
  74. package/build/server/chunks/{7-6XKPVOSu.js → 7-CsQZnkcG.js} +2 -2
  75. package/build/server/chunks/{7-6XKPVOSu.js.map → 7-CsQZnkcG.js.map} +1 -1
  76. package/build/server/chunks/{8-VOztiJG_.js → 8-DcIuPdyW.js} +2 -2
  77. package/build/server/chunks/{8-VOztiJG_.js.map → 8-DcIuPdyW.js.map} +1 -1
  78. package/build/server/chunks/{9-DNUlBLEz.js → 9-D8bkM1uj.js} +2 -2
  79. package/build/server/chunks/{9-DNUlBLEz.js.map → 9-D8bkM1uj.js.map} +1 -1
  80. package/build/server/chunks/{_page.svelte-DDE2nChH.js → _page.svelte-BBbaKwNz.js} +101 -27
  81. package/build/server/chunks/_page.svelte-BBbaKwNz.js.map +1 -0
  82. package/build/server/chunks/{_server.ts-DfscXcFe.js → _server.ts-B7BLxK5u.js} +233 -186
  83. package/build/server/chunks/_server.ts-B7BLxK5u.js.map +1 -0
  84. package/build/server/chunks/{_server.ts-EJVmhLtg.js → _server.ts-BSS8cO80.js} +15 -3
  85. package/build/server/chunks/_server.ts-BSS8cO80.js.map +1 -0
  86. package/build/server/chunks/_server.ts-DNTxPoxO.js +115 -0
  87. package/build/server/chunks/_server.ts-DNTxPoxO.js.map +1 -0
  88. package/build/server/chunks/{_server.ts-D9_hkPQ6.js → _server.ts-DUb7fbuW.js} +17 -3
  89. package/build/server/chunks/_server.ts-DUb7fbuW.js.map +1 -0
  90. package/build/server/chunks/{_server.ts-v7TaT83B.js → _server.ts-FdKi8RwL.js} +7 -3
  91. package/build/server/chunks/_server.ts-FdKi8RwL.js.map +1 -0
  92. package/build/server/chunks/device-format-DTgEz4Yr.js +29 -0
  93. package/build/server/chunks/device-format-DTgEz4Yr.js.map +1 -0
  94. package/build/server/chunks/device-token-store-Ct7aTeR8.js +259 -0
  95. package/build/server/chunks/device-token-store-Ct7aTeR8.js.map +1 -0
  96. package/build/server/chunks/{library-apns-D8RPINlv.js → library-apns-DMlL1BAg.js} +158 -26
  97. package/build/server/chunks/library-apns-DMlL1BAg.js.map +1 -0
  98. package/build/server/index.js +1 -1
  99. package/build/server/index.js.map +1 -1
  100. package/build/server/manifest.js +17 -17
  101. package/build/server/manifest.js.map +1 -1
  102. package/package.json +2 -2
  103. package/server.ts +15 -1
  104. package/src/app.d.ts +4 -0
  105. package/src/lib/modules/server/apn/apns-classify.ts +103 -0
  106. package/src/lib/modules/server/apn/library-apns.ts +151 -35
  107. package/src/lib/modules/server/apn/notify-fanout.ts +40 -0
  108. package/src/lib/modules/server/fcm/fcm-classify.ts +61 -0
  109. package/src/lib/modules/server/fcm/fcm-service.ts +128 -29
  110. package/src/lib/modules/server/push/device-format.ts +42 -0
  111. package/src/lib/modules/server/push/device-token-store.ts +354 -0
  112. package/src/lib/types/apn.ts +4 -0
  113. package/src/lib/types/device.ts +156 -0
  114. package/src/lib/types/index.ts +1 -0
  115. package/src/routes/api/debug/+server.ts +13 -1
  116. package/src/routes/api/device-token/+server.ts +122 -37
  117. package/src/routes/api/health/+server.ts +16 -2
  118. package/src/routes/api/notify/+server.ts +175 -168
  119. package/src/routes/api/qr-config/+server.ts +9 -2
  120. package/src/routes/config/+page.svelte +182 -44
  121. package/build/client/_app/immutable/assets/4.D4EDSN4H.css.br +0 -0
  122. package/build/client/_app/immutable/assets/4.D4EDSN4H.css.gz +0 -0
  123. package/build/client/_app/immutable/chunks/BzW0vlVX.js +0 -3
  124. package/build/client/_app/immutable/chunks/BzW0vlVX.js.br +0 -0
  125. package/build/client/_app/immutable/chunks/BzW0vlVX.js.gz +0 -0
  126. package/build/client/_app/immutable/chunks/CfzjLyJm.js.br +0 -0
  127. package/build/client/_app/immutable/chunks/CfzjLyJm.js.gz +0 -0
  128. package/build/client/_app/immutable/chunks/DjWRwZyr.js.br +0 -0
  129. package/build/client/_app/immutable/chunks/DjWRwZyr.js.gz +0 -0
  130. package/build/client/_app/immutable/entry/app.9F0rhLIY.js.br +0 -0
  131. package/build/client/_app/immutable/entry/app.9F0rhLIY.js.gz +0 -0
  132. package/build/client/_app/immutable/entry/start.DGFWmhrj.js +0 -1
  133. package/build/client/_app/immutable/entry/start.DGFWmhrj.js.br +0 -2
  134. package/build/client/_app/immutable/entry/start.DGFWmhrj.js.gz +0 -0
  135. package/build/client/_app/immutable/nodes/0.MlMcxYLT.js.br +0 -0
  136. package/build/client/_app/immutable/nodes/0.MlMcxYLT.js.gz +0 -0
  137. package/build/client/_app/immutable/nodes/1._Bn-HQ9b.js.br +0 -0
  138. package/build/client/_app/immutable/nodes/1._Bn-HQ9b.js.gz +0 -0
  139. package/build/client/_app/immutable/nodes/10.CsX0V4R9.js.br +0 -0
  140. package/build/client/_app/immutable/nodes/10.CsX0V4R9.js.gz +0 -0
  141. package/build/client/_app/immutable/nodes/11.lM_b6yUv.js.br +0 -0
  142. package/build/client/_app/immutable/nodes/11.lM_b6yUv.js.gz +0 -0
  143. package/build/client/_app/immutable/nodes/2.sAnHf5go.js.br +0 -0
  144. package/build/client/_app/immutable/nodes/2.sAnHf5go.js.gz +0 -0
  145. package/build/client/_app/immutable/nodes/3.DEHWta3G.js.br +0 -0
  146. package/build/client/_app/immutable/nodes/3.DEHWta3G.js.gz +0 -0
  147. package/build/client/_app/immutable/nodes/4.C9fv_m2R.js +0 -16
  148. package/build/client/_app/immutable/nodes/4.C9fv_m2R.js.br +0 -0
  149. package/build/client/_app/immutable/nodes/4.C9fv_m2R.js.gz +0 -0
  150. package/build/client/_app/immutable/nodes/6.-gDjaLSz.js.br +0 -0
  151. package/build/client/_app/immutable/nodes/6.-gDjaLSz.js.gz +0 -0
  152. package/build/client/_app/immutable/nodes/7.R-emDnvX.js.br +0 -0
  153. package/build/client/_app/immutable/nodes/7.R-emDnvX.js.gz +0 -0
  154. package/build/client/_app/immutable/nodes/8.DXNcFetv.js.br +0 -0
  155. package/build/client/_app/immutable/nodes/8.DXNcFetv.js.gz +0 -0
  156. package/build/client/_app/immutable/nodes/9.Brv6N-Ji.js.br +0 -0
  157. package/build/client/_app/immutable/nodes/9.Brv6N-Ji.js.gz +0 -0
  158. package/build/server/chunks/_page.svelte-DDE2nChH.js.map +0 -1
  159. package/build/server/chunks/_server.ts-D2RS8TFd.js +0 -72
  160. package/build/server/chunks/_server.ts-D2RS8TFd.js.map +0 -1
  161. package/build/server/chunks/_server.ts-D9_hkPQ6.js.map +0 -1
  162. package/build/server/chunks/_server.ts-DfscXcFe.js.map +0 -1
  163. package/build/server/chunks/_server.ts-EJVmhLtg.js.map +0 -1
  164. package/build/server/chunks/_server.ts-v7TaT83B.js.map +0 -1
  165. package/build/server/chunks/library-apns-D8RPINlv.js.map +0 -1
@@ -60,7 +60,9 @@ if (!process.env.API_KEY && !process.env.SHOOTER_API_KEY) {
60
60
 
61
61
  // Authentication
62
62
  const API_KEY = process.env.API_KEY || process.env.SHOOTER_API_KEY;
63
- const DEVICE_TOKEN = process.env.SHOOTER_DEVICE_TOKEN || null;
63
+ // SHOOTER_DEVICE_TOKEN is removed: the server now fans out to every device in
64
+ // its registry (see /api/device-token). Set DEVICE_TOKEN in the server's .env
65
+ // if you still want a single env-seeded fallback target.
64
66
  const AUTH_KEY = API_KEY || '';
65
67
 
66
68
  // Validate required environment variables for Claude Code and Codex CLI modes
@@ -1678,7 +1680,6 @@ function sendNotificationAndPoll(
1678
1680
  message: finalBody,
1679
1681
  waitForResponse: true,
1680
1682
  ...(skipPush ? { skipPush: true } : {}),
1681
- ...(DEVICE_TOKEN && { deviceToken: DEVICE_TOKEN }),
1682
1683
  ...(notificationCategory && { notificationCategory }),
1683
1684
  ...(question !== undefined && { question }),
1684
1685
  ...(options && { options }),
@@ -1796,6 +1797,20 @@ function sendNotificationAndPoll(
1796
1797
  return;
1797
1798
  }
1798
1799
 
1800
+ // 200 + success:false means the server reached 0 devices (none registered
1801
+ // or all tokens dead). No device can answer — fall through immediately
1802
+ // instead of polling for the full permission timeout (the 120s hang).
1803
+ try {
1804
+ const body = JSON.parse(responseData);
1805
+ if (body && body.success === false) {
1806
+ debugLog('Notification reached 0 devices (success:false) - falling through to local dialog');
1807
+ resolve(null);
1808
+ return;
1809
+ }
1810
+ } catch {
1811
+ // Non-JSON 200 body — proceed to poll as before.
1812
+ }
1813
+
1799
1814
  // Step 2: Start polling for user response
1800
1815
  startPolling(requestId, resolve);
1801
1816
  });
@@ -1864,6 +1879,18 @@ function startPolling(requestId, resolve) {
1864
1879
  res.on('end', () => {
1865
1880
  if (resolved) return;
1866
1881
 
1882
+ // 404 → the pending request is gone (expired, or another device already
1883
+ // answered and the row was consumed). Terminal: stop polling and fall
1884
+ // through to the local dialog instead of hanging until the timeout.
1885
+ if (res.statusCode === 404) {
1886
+ resolved = true;
1887
+ clearInterval(pollTimer);
1888
+ clearTimeout(overallTimeout);
1889
+ debugLog(`Pending request not found (HTTP 404, ${elapsed}s) - falling through to local dialog`);
1890
+ resolve(null);
1891
+ return;
1892
+ }
1893
+
1867
1894
  try {
1868
1895
  const result = JSON.parse(data);
1869
1896
  if (result.status === 'decided' && result.decision) {
@@ -1881,7 +1908,9 @@ function startPolling(requestId, resolve) {
1881
1908
 
1882
1909
  resolve({ decision: result.decision });
1883
1910
  }
1884
- // status === 'pending' keep polling
1911
+ // The "request gone" case is the HTTP 404 above; GET /api/response
1912
+ // never returns 200 with a not_found body, so there is no such branch
1913
+ // here. Any other 200 status (i.e. 'pending') → keep polling.
1885
1914
  } catch (e) {
1886
1915
  debugLog(`Poll parse error: ${e.message}`);
1887
1916
  }
@@ -1931,7 +1960,6 @@ function sendNotification(
1931
1960
  title,
1932
1961
  ...(subtitle ? { subtitle } : {}),
1933
1962
  message: finalBody,
1934
- ...(DEVICE_TOKEN && { deviceToken: DEVICE_TOKEN }),
1935
1963
  ...(extras.waitForResponse && { waitForResponse: true }),
1936
1964
  ...(extras.notificationCategory && { notificationCategory: extras.notificationCategory }),
1937
1965
  ...(extras.question !== undefined && { question: extras.question }),
@@ -0,0 +1,148 @@
1
+ // Service-manager lifecycle helpers for the `shooter` CLI.
2
+ //
3
+ // Background: the LaunchAgent/systemd unit runs `shooter start` in the
4
+ // foreground. The old plist used KeepAlive={SuccessfulExit:false}, so a clean
5
+ // SIGTERM (the parent forwards it, the server exits 0) was treated as a
6
+ // successful exit and deliberately NOT restarted — any clean stop killed the
7
+ // daemon until the next login.
8
+ //
9
+ // Fix: let the service manager own restart. KeepAlive=true / Restart=always
10
+ // restart on ANY exit; a deliberate stop is expressed by removing the job from
11
+ // the manager (`launchctl bootout` / `systemctl stop`), not by exit code.
12
+ //
13
+ // This module holds the pure decision logic and unit-file generation so they
14
+ // can be unit-tested without touching launchctl/systemctl. The impure plumbing
15
+ // (running launchctl, detecting managed context) stays in bin/shooter.cjs.
16
+
17
+ 'use strict';
18
+
19
+ const { xmlEscapeText } = require('./tunnel-discovery.cjs');
20
+
21
+ // Env marker injected into the generated unit so the manager-spawned
22
+ // `shooter start` runs the server directly instead of recursively delegating.
23
+ const MANAGED_ENV = 'SHOOTER_MANAGED';
24
+
25
+ /**
26
+ * Decide what `shooter start` should do.
27
+ *
28
+ * @param {object} o
29
+ * @param {boolean} o.managed - this process was spawned by the manager
30
+ * @param {boolean} o.agentInstalled - a unit file for the service exists on disk
31
+ * @param {string} o.platform - process.platform / os.platform()
32
+ * @param {boolean} [o.hasRuntimeOverrides] - start flags (--port/--daemon/--no-tunnel) the static unit can't honour
33
+ * @returns {'run-server'|'delegate-launchd'|'delegate-systemd'}
34
+ */
35
+ function resolveStartAction({ managed, agentInstalled, platform, hasRuntimeOverrides }) {
36
+ // The manager-spawned instance must run the server directly — otherwise the
37
+ // unit's `shooter start` would delegate to itself forever.
38
+ if (managed) return 'run-server';
39
+ // No unit installed → dev/manual run; just run the server.
40
+ if (!agentInstalled) return 'run-server';
41
+ // A one-off invocation carrying start flags the static unit can't express
42
+ // (--port/--daemon/--no-tunnel) must run directly rather than delegate and
43
+ // silently drop them.
44
+ if (hasRuntimeOverrides) return 'run-server';
45
+ // A real CLI invocation with a unit installed: hand off to the manager so the
46
+ // OS owns restart (and a prior `stop`/bootout gets re-bootstrapped).
47
+ if (platform === 'darwin') return 'delegate-launchd';
48
+ if (platform === 'linux') return 'delegate-systemd';
49
+ return 'run-server';
50
+ }
51
+
52
+ /**
53
+ * Decide what `shooter stop` should do.
54
+ *
55
+ * @param {object} o
56
+ * @param {boolean} o.agentManaging - a manager currently owns the service
57
+ * @param {string} o.platform - process.platform / os.platform()
58
+ * @returns {'bootout'|'systemctl-stop'|'pid-kill'}
59
+ */
60
+ function resolveStopAction({ agentManaging, platform }) {
61
+ // Dev/manual run (no manager) → kill the pidfile process.
62
+ if (!agentManaging) return 'pid-kill';
63
+ // Remove the job from the manager so KeepAlive/Restart can't resurrect it.
64
+ if (platform === 'darwin') return 'bootout';
65
+ if (platform === 'linux') return 'systemctl-stop';
66
+ return 'pid-kill';
67
+ }
68
+
69
+ /**
70
+ * Build the macOS LaunchAgent plist.
71
+ *
72
+ * KeepAlive is unconditional (`<true/>`) so any exit — crash, kill, or an
73
+ * accidental clean SIGTERM — is restarted. The SHOOTER_MANAGED marker lets the
74
+ * spawned instance know not to re-delegate.
75
+ */
76
+ function buildLaunchdPlist({ label, nodeBin, shooterBin, pkgRoot, pathEnv, shooterHome, logFile }) {
77
+ const e = xmlEscapeText;
78
+ return `<?xml version="1.0" encoding="UTF-8"?>
79
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
80
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
81
+ <plist version="1.0">
82
+ <dict>
83
+ <key>Label</key>
84
+ <string>${e(label)}</string>
85
+ <key>ProgramArguments</key>
86
+ <array>
87
+ <string>${e(nodeBin)}</string>
88
+ <string>${e(shooterBin)}</string>
89
+ <string>start</string>
90
+ </array>
91
+ <key>WorkingDirectory</key>
92
+ <string>${e(pkgRoot)}</string>
93
+ <key>EnvironmentVariables</key>
94
+ <dict>
95
+ <key>PATH</key>
96
+ <string>${e(pathEnv)}</string>
97
+ <key>SHOOTER_HOME</key>
98
+ <string>${e(shooterHome)}</string>
99
+ <key>${MANAGED_ENV}</key>
100
+ <string>1</string>
101
+ </dict>
102
+ <key>KeepAlive</key>
103
+ <true/>
104
+ <key>RunAtLoad</key>
105
+ <true/>
106
+ <key>StandardOutPath</key>
107
+ <string>${e(logFile)}</string>
108
+ <key>StandardErrorPath</key>
109
+ <string>${e(logFile)}</string>
110
+ <key>ThrottleInterval</key>
111
+ <integer>10</integer>
112
+ </dict>
113
+ </plist>`;
114
+ }
115
+
116
+ /**
117
+ * Build the Linux systemd user unit.
118
+ *
119
+ * Restart=always (not on-failure) so a clean exit also recovers; a deliberate
120
+ * `systemctl --user stop` keeps it stopped. The SHOOTER_MANAGED marker mirrors
121
+ * the launchd path.
122
+ */
123
+ function buildSystemdUnit({ nodeBin, shooterBin, pkgRoot, shooterHome }) {
124
+ return `[Unit]
125
+ Description=Shooter — Mobile dev notifications & remote terminal
126
+ After=network.target
127
+
128
+ [Service]
129
+ Type=simple
130
+ ExecStart="${nodeBin}" "${shooterBin}" start
131
+ WorkingDirectory="${pkgRoot}"
132
+ Environment="SHOOTER_HOME=${shooterHome}"
133
+ Environment="${MANAGED_ENV}=1"
134
+ Restart=always
135
+ RestartSec=10
136
+
137
+ [Install]
138
+ WantedBy=default.target
139
+ `;
140
+ }
141
+
142
+ module.exports = {
143
+ MANAGED_ENV,
144
+ resolveStartAction,
145
+ resolveStopAction,
146
+ buildLaunchdPlist,
147
+ buildSystemdUnit,
148
+ };
package/bin/shooter.cjs CHANGED
@@ -13,8 +13,14 @@ const {
13
13
  discoverNamedTunnels,
14
14
  healAndEnsureRunning,
15
15
  probeReachability,
16
- xmlEscapeText,
17
16
  } = require('./lib/tunnel-discovery.cjs');
17
+ const {
18
+ MANAGED_ENV,
19
+ resolveStartAction,
20
+ resolveStopAction,
21
+ buildLaunchdPlist,
22
+ buildSystemdUnit,
23
+ } = require('./lib/service-manager.cjs');
18
24
 
19
25
  // ── Resolve paths ───────────────────────────────────────────────────
20
26
  const PKG_ROOT = path.resolve(__dirname, '..');
@@ -315,6 +321,24 @@ function startServer() {
315
321
  process.exit(1);
316
322
  }
317
323
 
324
+ // Service-manager delegation: a real `shooter start` (not spawned by the
325
+ // service manager) hands off to launchd/systemd so the OS owns restart and
326
+ // recovers from any clean SIGTERM. The manager-spawned instance carries
327
+ // SHOOTER_MANAGED=1 (or ppid 1 on macOS) and falls through to run directly.
328
+ // Runtime start flags the static unit can't express must bypass delegation so
329
+ // they aren't silently dropped (e.g. `shooter start --port 9000` on a machine
330
+ // with autostart installed).
331
+ const hasRuntimeOverrides =
332
+ parsePortFlag() !== undefined || hasFlag('--daemon') || hasFlag('-d') || hasFlag('--no-tunnel');
333
+ const startAction = resolveStartAction({
334
+ managed: isSpawnedByManager(),
335
+ agentInstalled: isAutostartInstalled(),
336
+ platform: os.platform(),
337
+ hasRuntimeOverrides,
338
+ });
339
+ if (startAction === 'delegate-launchd') return delegateStartToLaunchd();
340
+ if (startAction === 'delegate-systemd') return delegateStartToSystemd();
341
+
318
342
  // Check if already running
319
343
  const existingPid = readPid();
320
344
  if (existingPid) {
@@ -502,6 +526,16 @@ function startServer() {
502
526
  // ── stop ────────────────────────────────────────────────────────────
503
527
 
504
528
  function stopServer() {
529
+ // Manager-owned services stop by removing the job from the manager (bootout /
530
+ // systemctl stop) so KeepAlive/Restart can't immediately resurrect it.
531
+ // Dev/manual runs fall through to the pidfile path below.
532
+ const stopAction = resolveStopAction({
533
+ agentManaging: isLaunchdManaging() || isSystemdManaging(),
534
+ platform: os.platform(),
535
+ });
536
+ if (stopAction === 'bootout') return stopViaLaunchd();
537
+ if (stopAction === 'systemctl-stop') return stopViaSystemd();
538
+
505
539
  const pid = readPid();
506
540
  if (!pid) {
507
541
  // Still try to stop tunnel even if server isn't running
@@ -682,48 +716,17 @@ function enableLaunchAgent() {
682
716
  const shooterBin = resolveShooterBin();
683
717
  const nodeBin = process.execPath;
684
718
 
685
- // All five interpolations below are system-derived paths (no user
686
- // input), but XML-escaping them keeps the plist well-formed if any
687
- // path ever contains `&`, `<`, or `>` — same hardening as the named-
688
- // tunnel rewrite path.
689
- const e = xmlEscapeText;
690
- const plist = `<?xml version="1.0" encoding="UTF-8"?>
691
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
692
- "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
693
- <plist version="1.0">
694
- <dict>
695
- <key>Label</key>
696
- <string>${LAUNCHD_LABEL}</string>
697
- <key>ProgramArguments</key>
698
- <array>
699
- <string>${e(nodeBin)}</string>
700
- <string>${e(shooterBin)}</string>
701
- <string>start</string>
702
- </array>
703
- <key>WorkingDirectory</key>
704
- <string>${e(PKG_ROOT)}</string>
705
- <key>EnvironmentVariables</key>
706
- <dict>
707
- <key>PATH</key>
708
- <string>${e(path.dirname(nodeBin))}:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
709
- <key>SHOOTER_HOME</key>
710
- <string>${e(SHOOTER_HOME)}</string>
711
- </dict>
712
- <key>KeepAlive</key>
713
- <dict>
714
- <key>SuccessfulExit</key>
715
- <false/>
716
- </dict>
717
- <key>RunAtLoad</key>
718
- <true/>
719
- <key>StandardOutPath</key>
720
- <string>${e(LOG_FILE)}</string>
721
- <key>StandardErrorPath</key>
722
- <string>${e(LOG_FILE)}</string>
723
- <key>ThrottleInterval</key>
724
- <integer>10</integer>
725
- </dict>
726
- </plist>`;
719
+ // Path interpolations are XML-escaped inside buildLaunchdPlist so a path
720
+ // containing `&`, `<`, or `>` can't corrupt the plist.
721
+ const plist = buildLaunchdPlist({
722
+ label: LAUNCHD_LABEL,
723
+ nodeBin,
724
+ shooterBin,
725
+ pkgRoot: PKG_ROOT,
726
+ pathEnv: `${path.dirname(nodeBin)}:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin`,
727
+ shooterHome: SHOOTER_HOME,
728
+ logFile: LOG_FILE,
729
+ });
727
730
 
728
731
  // Ensure directories exist
729
732
  fs.mkdirSync(path.dirname(LAUNCHD_PLIST), { recursive: true });
@@ -764,25 +767,15 @@ function disableLaunchAgent() {
764
767
  function enableSystemdUnit() {
765
768
  const shooterBin = resolveShooterBin();
766
769
 
767
- // Quote interpolated paths so systemd's word-splitter doesn't treat
768
- // a space inside e.g. "/Users/John Doe/.local/bin/shooter" as an
769
- // argument separator. systemd supports double-quoting per token in
770
- // ExecStart= and the whole RHS in Environment=.
771
- const unit = `[Unit]
772
- Description=Shooter — Mobile dev notifications & remote terminal
773
- After=network.target
774
-
775
- [Service]
776
- Type=simple
777
- ExecStart="${process.execPath}" "${shooterBin}" start
778
- WorkingDirectory="${PKG_ROOT}"
779
- Environment="SHOOTER_HOME=${SHOOTER_HOME}"
780
- Restart=on-failure
781
- RestartSec=10
782
-
783
- [Install]
784
- WantedBy=default.target
785
- `;
770
+ // Paths are double-quoted inside buildSystemdUnit so systemd's word-splitter
771
+ // doesn't treat a space inside e.g. "/Users/John Doe/.local/bin/shooter" as
772
+ // an argument separator.
773
+ const unit = buildSystemdUnit({
774
+ nodeBin: process.execPath,
775
+ shooterBin,
776
+ pkgRoot: PKG_ROOT,
777
+ shooterHome: SHOOTER_HOME,
778
+ });
786
779
 
787
780
  fs.mkdirSync(path.dirname(SYSTEMD_UNIT), { recursive: true });
788
781
  fs.mkdirSync(LOG_DIR, { recursive: true });
@@ -1153,6 +1146,102 @@ function isLaunchdManaging() {
1153
1146
  }
1154
1147
  }
1155
1148
 
1149
+ function isSystemdManaging() {
1150
+ if (os.platform() !== 'linux') return false;
1151
+ try {
1152
+ execFileSync('systemctl', ['--user', 'is-active', '--quiet', 'shooter.service'], {
1153
+ stdio: 'ignore',
1154
+ timeout: 5_000,
1155
+ });
1156
+ return true;
1157
+ } catch {
1158
+ return false;
1159
+ }
1160
+ }
1161
+
1162
+ // True when this process was spawned by the service manager — so `shooter start`
1163
+ // runs the server directly instead of delegating back to the manager.
1164
+ function isSpawnedByManager() {
1165
+ // Primary signal: the marker env injected into the generated unit.
1166
+ if (process.env[MANAGED_ENV] === '1') return true;
1167
+ // Best-effort transition fallback for an older, marker-less plist still on
1168
+ // disk: launchd reparents its GUI agents to PID 1, so a launchd-spawned
1169
+ // `shooter start` has ppid 1 while a shell-run one does not. This is only a
1170
+ // heuristic (it won't hold if shooter is wrapped by e.g. sudo) and exists
1171
+ // solely to avoid delegate-recursion until the plist is regenerated with the
1172
+ // SHOOTER_MANAGED marker — it can be dropped once all installs carry it.
1173
+ if (os.platform() === 'darwin' && process.ppid === 1) return true;
1174
+ return false;
1175
+ }
1176
+
1177
+ // Hand a manual `shooter start` off to launchd. If the job is already loaded it
1178
+ // is already running (KeepAlive); otherwise bootstrap it (e.g. after a stop).
1179
+ function delegateStartToLaunchd() {
1180
+ const uid = process.getuid ? process.getuid() : 501;
1181
+ // bootstrap if the job isn't loaded; if it is, `kickstart -k` restarts it so
1182
+ // `shooter start` recycles a loaded-but-stale agent instead of no-opping.
1183
+ const loaded = isLaunchdManaging();
1184
+ try {
1185
+ if (loaded) {
1186
+ execFileSync('launchctl', ['kickstart', '-k', `gui/${uid}/${LAUNCHD_LABEL}`], {
1187
+ stdio: 'ignore',
1188
+ });
1189
+ } else {
1190
+ execFileSync('launchctl', ['bootstrap', `gui/${uid}`, LAUNCHD_PLIST], { stdio: 'ignore' });
1191
+ }
1192
+ } catch (err) {
1193
+ console.error(`Failed to start via launchd: ${err.message}`);
1194
+ console.error('Try: shooter autostart on');
1195
+ process.exit(1);
1196
+ }
1197
+ console.log(`Shooter ${loaded ? 'restarted' : 'started'} (launchd-managed).`);
1198
+ console.log(' Logs: shooter logs');
1199
+ }
1200
+
1201
+ function delegateStartToSystemd() {
1202
+ // restart if the unit is active (recycle a stale instance), else start it.
1203
+ const active = isSystemdManaging();
1204
+ const action = active ? 'restart' : 'start';
1205
+ try {
1206
+ execFileSync('systemctl', ['--user', action, 'shooter.service'], { stdio: 'ignore' });
1207
+ } catch (err) {
1208
+ console.error(`Failed to start via systemd: ${err.message}`);
1209
+ console.error('Try: shooter autostart on');
1210
+ process.exit(1);
1211
+ }
1212
+ console.log(`Shooter ${active ? 'restarted' : 'started'} (systemd-managed).`);
1213
+ console.log(' Logs: shooter logs');
1214
+ }
1215
+
1216
+ // Stop a launchd-managed service by removing the job from the domain. The plist
1217
+ // file stays on disk, so it returns on next login or `shooter start`.
1218
+ function stopViaLaunchd() {
1219
+ const uid = process.getuid ? process.getuid() : 501;
1220
+ console.log('Stopping Shooter (launchd-managed)...');
1221
+ stopTunnel();
1222
+ stopGuard();
1223
+ try {
1224
+ execFileSync('launchctl', ['bootout', `gui/${uid}/${LAUNCHD_LABEL}`], { stdio: 'ignore' });
1225
+ } catch {
1226
+ // Not loaded / already booted out — nothing to do.
1227
+ }
1228
+ removePid();
1229
+ console.log('Shooter stopped. (Returns on next login or "shooter start".)');
1230
+ }
1231
+
1232
+ function stopViaSystemd() {
1233
+ console.log('Stopping Shooter (systemd-managed)...');
1234
+ stopTunnel();
1235
+ stopGuard();
1236
+ try {
1237
+ execFileSync('systemctl', ['--user', 'stop', 'shooter.service'], { stdio: 'ignore' });
1238
+ } catch {
1239
+ // Not active — nothing to do.
1240
+ }
1241
+ removePid();
1242
+ console.log('Shooter stopped.');
1243
+ }
1244
+
1156
1245
  function runGuard() {
1157
1246
  // Parse guard-specific args
1158
1247
  let parentPid = 0;
@@ -1 +1 @@
1
- .settings-container.svelte-1gp6n77{max-width:900px;margin:0 auto;padding-bottom:var(--space-6)}.settings-grid.svelte-1gp6n77{display:grid;grid-template-columns:1fr 320px;gap:var(--space-6);align-items:start}.settings-section.svelte-1gp6n77,.settings-sidebar.svelte-1gp6n77{display:flex;flex-direction:column;gap:var(--space-4)}.button-group.svelte-1gp6n77{display:flex;gap:var(--space-3);justify-content:flex-end}.setup-stepper{--container-flex-direction: column;--step-flex-direction: row;--step-index-container-height: 24px;--step-index-container-width: 24px;--step-index-font-size: var(--text-xs);--step-text-font-size: var(--text-sm);--step-text-margin: 0 0 0 var(--space-3);--separator-display: none;--step-text-active-color: var(--text-primary);--step-text-completed-color: var(--ds-green-900);--step-index-container-active-background-color: var(--component-bg-active);--step-index-container-completed-background-color: var(--ds-green-700);--step-index-container-background-color: var(--component-bg-active);--step-index-color: var(--text-secondary);align-items:flex-start;gap:var(--space-3)}.qr-section.svelte-1gp6n77{display:flex;flex-direction:column;gap:var(--space-3)}.qr-section.svelte-1gp6n77 .button-container{align-self:center}.qr-container.svelte-1gp6n77{display:flex;justify-content:center;padding:var(--space-3);background:var(--ds-background-200);border:1px solid var(--border);border-radius:var(--radius-lg)}.qr-image.svelte-1gp6n77{width:200px;height:200px;border-radius:var(--radius-sm);image-rendering:pixelated}.qr-placeholder.svelte-1gp6n77{display:flex;align-items:center;justify-content:center;height:200px;background:var(--ds-background-200);border:1px dashed var(--border);border-radius:var(--radius-lg)}.qr-loading-text.svelte-1gp6n77{font-size:var(--text-sm);color:var(--text-tertiary)}.qr-hint.svelte-1gp6n77{font-size:var(--text-xs);color:var(--text-secondary);line-height:var(--leading-relaxed);text-align:center}.qr-server-url.svelte-1gp6n77{font-size:var(--text-xs);color:var(--text-tertiary);text-align:center}.qr-server-url.svelte-1gp6n77 code:where(.svelte-1gp6n77){font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-secondary);background:var(--component-bg);padding:1px 4px;border-radius:var(--radius-sm)}.qr-description.svelte-1gp6n77{font-size:var(--text-sm);color:var(--text-secondary);line-height:var(--leading-relaxed)}.qr-error.svelte-1gp6n77{font-size:var(--text-sm);color:var(--ds-red-900);line-height:var(--leading-relaxed)}.settings-sidebar.svelte-1gp6n77 .card:last-child{border-color:color-mix(in srgb,var(--ds-red-700) 30%,transparent)}.ai-providers.svelte-1gp6n77{display:flex;flex-direction:column;gap:var(--space-2)}.provider-row.svelte-1gp6n77{display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm)}.provider-label.svelte-1gp6n77{flex:1;color:var(--text-primary)}.provider-status.svelte-1gp6n77{font-size:var(--text-xs);color:var(--text-tertiary)}.provider-status.configured.svelte-1gp6n77{color:var(--ds-green-900)}.active-provider.svelte-1gp6n77{margin-top:var(--space-2);padding-top:var(--space-2);border-top:1px solid var(--border);font-size:var(--text-xs);color:var(--text-secondary)}.ai-help.svelte-1gp6n77{font-size:var(--text-xs);color:var(--text-secondary);margin-top:var(--space-2)}.ai-help.svelte-1gp6n77 code:where(.svelte-1gp6n77){background:var(--ds-gray-200, #1a1a1a);padding:1px 4px;border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:var(--text-xs)}.danger-description.svelte-1gp6n77{font-size:var(--text-sm);color:var(--text-secondary);margin-bottom:var(--space-4);line-height:var(--leading-relaxed);padding-left:var(--space-3);border-left:3px solid color-mix(in srgb,var(--ds-red-700) 50%,transparent)}.input-help.svelte-1gp6n77{font-size:var(--text-xs);color:var(--text-tertiary);margin-top:var(--space-1);margin-bottom:0}.input-help.svelte-1gp6n77 code:where(.svelte-1gp6n77){background:var(--ds-gray-200, #1a1a1a);padding:1px 4px;border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:var(--text-xs)}@media(max-width:768px){.settings-grid.svelte-1gp6n77{grid-template-columns:1fr}.button-group.svelte-1gp6n77{flex-direction:column}.button-group.svelte-1gp6n77 button,.button-group.svelte-1gp6n77 .button-container{width:100%}}
1
+ .settings-container.svelte-1gp6n77{max-width:900px;margin:0 auto;padding-bottom:var(--space-6)}.settings-grid.svelte-1gp6n77{display:grid;grid-template-columns:1fr 320px;gap:var(--space-6);align-items:start}.settings-section.svelte-1gp6n77,.settings-sidebar.svelte-1gp6n77{display:flex;flex-direction:column;gap:var(--space-4)}.button-group.svelte-1gp6n77{display:flex;gap:var(--space-3);justify-content:flex-end}.setup-stepper{--container-flex-direction: column;--step-flex-direction: row;--step-index-container-height: 24px;--step-index-container-width: 24px;--step-index-font-size: var(--text-xs);--step-text-font-size: var(--text-sm);--step-text-margin: 0 0 0 var(--space-3);--separator-display: none;--step-text-active-color: var(--text-primary);--step-text-completed-color: var(--ds-green-900);--step-index-container-active-background-color: var(--component-bg-active);--step-index-container-completed-background-color: var(--ds-green-700);--step-index-container-background-color: var(--component-bg-active);--step-index-color: var(--text-secondary);align-items:flex-start;gap:var(--space-3)}.qr-section.svelte-1gp6n77{display:flex;flex-direction:column;gap:var(--space-3)}.qr-section.svelte-1gp6n77 .button-container{align-self:center}.qr-container.svelte-1gp6n77{display:flex;justify-content:center;padding:var(--space-3);background:var(--ds-background-200);border:1px solid var(--border);border-radius:var(--radius-lg)}.qr-image.svelte-1gp6n77{width:200px;height:200px;border-radius:var(--radius-sm);image-rendering:pixelated}.qr-placeholder.svelte-1gp6n77{display:flex;align-items:center;justify-content:center;height:200px;background:var(--ds-background-200);border:1px dashed var(--border);border-radius:var(--radius-lg)}.qr-loading-text.svelte-1gp6n77{font-size:var(--text-sm);color:var(--text-tertiary)}.qr-hint.svelte-1gp6n77{font-size:var(--text-xs);color:var(--text-secondary);line-height:var(--leading-relaxed);text-align:center}.qr-server-url.svelte-1gp6n77{font-size:var(--text-xs);color:var(--text-tertiary);text-align:center}.qr-server-url.svelte-1gp6n77 code:where(.svelte-1gp6n77){font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-secondary);background:var(--component-bg);padding:1px 4px;border-radius:var(--radius-sm)}.qr-description.svelte-1gp6n77{font-size:var(--text-sm);color:var(--text-secondary);line-height:var(--leading-relaxed)}.qr-error.svelte-1gp6n77{font-size:var(--text-sm);color:var(--ds-red-900);line-height:var(--leading-relaxed)}.settings-sidebar.svelte-1gp6n77 .card:last-child{border-color:color-mix(in srgb,var(--ds-red-700) 30%,transparent)}.ai-providers.svelte-1gp6n77{display:flex;flex-direction:column;gap:var(--space-2)}.provider-row.svelte-1gp6n77{display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm)}.provider-label.svelte-1gp6n77{flex:1;color:var(--text-primary)}.provider-status.svelte-1gp6n77{font-size:var(--text-xs);color:var(--text-tertiary)}.provider-status.configured.svelte-1gp6n77{color:var(--ds-green-900)}.active-provider.svelte-1gp6n77{margin-top:var(--space-2);padding-top:var(--space-2);border-top:1px solid var(--border);font-size:var(--text-xs);color:var(--text-secondary)}.ai-help.svelte-1gp6n77{font-size:var(--text-xs);color:var(--text-secondary);margin-top:var(--space-2)}.ai-help.svelte-1gp6n77 code:where(.svelte-1gp6n77){background:var(--ds-gray-200, #1a1a1a);padding:1px 4px;border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:var(--text-xs)}.danger-description.svelte-1gp6n77{font-size:var(--text-sm);color:var(--text-secondary);margin-bottom:var(--space-4);line-height:var(--leading-relaxed);padding-left:var(--space-3);border-left:3px solid color-mix(in srgb,var(--ds-red-700) 50%,transparent)}.input-help.svelte-1gp6n77{font-size:var(--text-xs);color:var(--text-tertiary);margin-top:var(--space-1);margin-bottom:0}.input-help.svelte-1gp6n77 code:where(.svelte-1gp6n77){background:var(--ds-gray-200, #1a1a1a);padding:1px 4px;border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:var(--text-xs)}.device-list.svelte-1gp6n77{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--space-2)}.device-row.svelte-1gp6n77{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-2) 0;border-bottom:1px solid var(--ds-gray-200, #1a1a1a)}.device-meta.svelte-1gp6n77{display:flex;flex-direction:column;gap:2px;min-width:0}.device-name.svelte-1gp6n77{font-size:var(--text-sm);font-weight:600;display:flex;align-items:center;gap:var(--space-2)}.device-badge.svelte-1gp6n77{font-size:var(--text-xs);font-weight:500;color:var(--ds-green-900, #16a34a);border:1px solid var(--ds-green-700, #16a34a);border-radius:var(--radius-sm);padding:0 6px}.device-sub.svelte-1gp6n77{font-size:var(--text-xs);color:var(--text-tertiary);font-family:var(--font-mono)}@media(max-width:768px){.settings-grid.svelte-1gp6n77{grid-template-columns:1fr}.button-group.svelte-1gp6n77{flex-direction:column}.button-group.svelte-1gp6n77 button,.button-group.svelte-1gp6n77 .button-container{width:100%}}
@@ -1 +1 @@
1
- import{s as e}from"./BzW0vlVX.js";const r=()=>{const s=e;return{page:{subscribe:s.page.subscribe},navigating:{subscribe:s.navigating.subscribe},updated:s.updated}},b={subscribe(s){return r().page.subscribe(s)}};export{b as p};
1
+ import{s as e}from"./ssAhjWfF.js";const r=()=>{const s=e;return{page:{subscribe:s.page.subscribe},navigating:{subscribe:s.navigating.subscribe},updated:s.updated}},b={subscribe(s){return r().page.subscribe(s)}};export{b as p};
@@ -1 +1 @@
1
- import{s as r,p as e}from"./BzW0vlVX.js";const a={get params(){return e.params},get url(){return e.url}};r.updated.check;const s=a;export{s as p};
1
+ import{s as r,p as e}from"./ssAhjWfF.js";const a={get params(){return e.params},get url(){return e.url}};r.updated.check;const s=a;export{s as p};
@@ -0,0 +1,3 @@
1
+ import{bv as Pe,v as Ye,s as P,g as N,b as j,P as ie,bw as St}from"./kzeZOqU5.js";const Rt="1782544936668";class me{constructor(t,n){this.status=t,typeof n=="string"?this.body={message:n}:n?this.body=n:this.body={message:`Error: ${t}`}}toString(){return JSON.stringify(this.body)}}class Ne{constructor(t,n){try{new Headers({location:n})}catch{throw new Error(`Invalid redirect location ${JSON.stringify(n)}: this string contains characters that cannot be used in HTTP headers`)}this.status=t,this.location=n}}class je extends Error{constructor(t,n,r){super(r),this.status=t,this.text=n}}new URL("sveltekit-internal://");function It(e,t){return e==="/"||t==="ignore"?e:t==="never"?e.endsWith("/")?e.slice(0,-1):e:t==="always"&&!e.endsWith("/")?e+"/":e}function Tt(e){return e.split("%25").map(decodeURI).join("%25")}function Ut(e){for(const t in e)e[t]=decodeURIComponent(e[t]);return e}function ke({href:e}){return e.split("#")[0]}function $(){}function Lt(...e){let t=5381;for(const n of e)if(typeof n=="string"){let r=n.length;for(;r;)t=t*33^n.charCodeAt(--r)}else if(ArrayBuffer.isView(n)){const r=new Uint8Array(n.buffer,n.byteOffset,n.byteLength);let a=r.length;for(;a;)t=t*33^r[--a]}else throw new TypeError("value must be a string or TypedArray");return(t>>>0).toString(36)}new TextEncoder;function Pt(e){const t=atob(e),n=new Uint8Array(t.length);for(let r=0;r<t.length;r++)n[r]=t.charCodeAt(r);return n}const Nt=window.fetch;window.fetch=(e,t)=>((e instanceof Request?e.method:t?.method||"GET")!=="GET"&&z.delete(Oe(e)),Nt(e,t));const z=new Map;function jt(e,t){const n=Oe(e,t),r=document.querySelector(n);if(r?.textContent){r.remove();let{body:a,...i}=JSON.parse(r.textContent);const o=r.getAttribute("data-ttl");return o&&z.set(n,{body:a,init:i,ttl:1e3*Number(o)}),r.getAttribute("data-b64")!==null&&(a=Pt(a)),Promise.resolve(new Response(a,i))}return window.fetch(e,t)}function Ot(e,t,n){if(z.size>0){const r=Oe(e,n),a=z.get(r);if(a){if(performance.now()<a.ttl&&["default","force-cache","only-if-cached",void 0].includes(n?.cache))return new Response(a.body,a.init);z.delete(r)}}return window.fetch(t,n)}function Oe(e,t){let r=`script[data-sveltekit-fetched][data-url=${JSON.stringify(e instanceof Request?e.url:e)}]`;if(t?.headers||t?.body){const a=[];t.headers&&a.push([...new Headers(t.headers)].join(",")),t.body&&(typeof t.body=="string"||ArrayBuffer.isView(t.body))&&a.push(t.body),r+=`[data-hash="${Lt(...a)}"]`}return r}const xt=/^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;function Ct(e){const t=[];return{pattern:e==="/"?/^\/$/:new RegExp(`^${Dt(e).map(r=>{const a=/^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(r);if(a)return t.push({name:a[1],matcher:a[2],optional:!1,rest:!0,chained:!0}),"(?:/([^]*))?";const i=/^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(r);if(i)return t.push({name:i[1],matcher:i[2],optional:!0,rest:!1,chained:!0}),"(?:/([^/]+))?";if(!r)return;const o=r.split(/\[(.+?)\](?!\])/);return"/"+o.map((s,l)=>{if(l%2){if(s.startsWith("x+"))return Ee(String.fromCharCode(parseInt(s.slice(2),16)));if(s.startsWith("u+"))return Ee(String.fromCharCode(...s.slice(2).split("-").map(m=>parseInt(m,16))));const f=xt.exec(s),[,d,p,h,u]=f;return t.push({name:h,matcher:u,optional:!!d,rest:!!p,chained:p?l===1&&o[0]==="":!1}),p?"([^]*?)":d?"([^/]*)?":"([^/]+?)"}return Ee(s)}).join("")}).join("")}/?$`),params:t}}function $t(e){return e!==""&&!/^\([^)]+\)$/.test(e)}function Dt(e){return e.slice(1).split("/").filter($t)}function Bt(e,t,n){const r={},a=e.slice(1),i=a.filter(c=>c!==void 0);let o=0;for(let c=0;c<t.length;c+=1){const s=t[c];let l=a[c-o];if(s.chained&&s.rest&&o&&(l=a.slice(c-o,c+1).filter(f=>f).join("/"),o=0),l===void 0)if(s.rest)l="";else continue;if(!s.matcher||n[s.matcher](l)){r[s.name]=l;const f=t[c+1],d=a[c+1];f&&!f.rest&&f.optional&&d&&s.chained&&(o=0),!f&&!d&&Object.keys(r).length===i.length&&(o=0);continue}if(s.optional&&s.chained){o++;continue}return}if(!o)return r}function Ee(e){return e.normalize().replace(/[[\]]/g,"\\$&").replace(/%/g,"%25").replace(/\//g,"%2[Ff]").replace(/\?/g,"%3[Ff]").replace(/#/g,"%23").replace(/[.*+?^${}()|\\]/g,"\\$&")}function Mt({nodes:e,server_loads:t,dictionary:n,matchers:r}){const a=new Set(t);return Object.entries(n).map(([c,[s,l,f]])=>{const{pattern:d,params:p}=Ct(c),h={id:c,exec:u=>{const m=d.exec(u);if(m)return Bt(m,p,r)},errors:[1,...f||[]].map(u=>e[u]),layouts:[0,...l||[]].map(o),leaf:i(s)};return h.errors.length=h.layouts.length=Math.max(h.errors.length,h.layouts.length),h});function i(c){const s=c<0;return s&&(c=~c),[s,e[c]]}function o(c){return c===void 0?c:[a.has(c),e[c]]}}function tt(e,t=JSON.parse){try{return t(sessionStorage[e])}catch{}}function Ge(e,t,n=JSON.stringify){const r=n(t);try{sessionStorage[e]=r}catch{}}const L=globalThis.__sveltekit_1f9h0ew?.base??"",Ft=globalThis.__sveltekit_1f9h0ew?.assets??L??"",nt="sveltekit:snapshot",rt="sveltekit:scroll",at="sveltekit:states",Vt="sveltekit:pageurl",Y="sveltekit:history",ee="sveltekit:navigation",F={tap:1,hover:2,viewport:3,eager:4,off:-1,false:-1},ae=location.origin;function xe(e){if(e instanceof URL)return e;let t=document.baseURI;if(!t){const n=document.getElementsByTagName("base");t=n.length?n[0].href:document.URL}return new URL(e,t)}function V(){return{x:pageXOffset,y:pageYOffset}}function q(e,t){return e.getAttribute(`data-sveltekit-${t}`)}const He={...F,"":F.hover};function ot(e){let t=e.assignedSlot??e.parentNode;return t?.nodeType===11&&(t=t.host),t}function st(e,t){for(;e&&e!==t;){if(e.nodeName.toUpperCase()==="A"&&e.hasAttribute("href"))return e;e=ot(e)}}function Re(e,t,n){let r;try{if(r=new URL(e instanceof SVGAElement?e.href.baseVal:e.href,document.baseURI),n&&r.hash.match(/^#[^/]/)){const c=location.hash.split("#")[1]||"/";r.hash=`#${c}${r.hash}`}}catch{}const a=e instanceof SVGAElement?e.target.baseVal:e.target,i=!r||!!a||we(r,t,n)||(e.getAttribute("rel")||"").split(/\s+/).includes("external"),o=r?.origin===ae&&e.hasAttribute("download");return{url:r,external:i,target:a,download:o}}function ce(e){let t=null,n=null,r=null,a=null,i=null,o=null,c=e;for(;c&&c!==document.documentElement;)r===null&&(r=q(c,"preload-code")),a===null&&(a=q(c,"preload-data")),t===null&&(t=q(c,"keepfocus")),n===null&&(n=q(c,"noscroll")),i===null&&(i=q(c,"reload")),o===null&&(o=q(c,"replacestate")),c=ot(c);function s(l){switch(l){case"":case"true":return!0;case"off":case"false":return!1;default:return}}return{preload_code:He[r??"off"],preload_data:He[a??"off"],keepfocus:s(t),noscroll:s(n),reload:s(i),replace_state:s(o)}}function Ke(e){const t=Pe(e);let n=!0;function r(){n=!0,t.update(o=>o)}function a(o){n=!1,t.set(o)}function i(o){let c;return t.subscribe(s=>{(c===void 0||n&&s!==c)&&o(c=s)})}return{notify:r,set:a,subscribe:i}}const it={v:$};function qt(){const{set:e,subscribe:t}=Pe(!1);let n;async function r(){clearTimeout(n);try{const a=await fetch(`${Ft}/_app/version.json`,{headers:{pragma:"no-cache","cache-control":"no-cache"}});if(!a.ok)return!1;const o=(await a.json()).version!==Rt;return o&&(e(!0),it.v(),clearTimeout(n)),o}catch{return!1}}return{subscribe:t,check:r}}function we(e,t,n){return e.origin!==ae||!e.pathname.startsWith(t)?!0:n?e.pathname!==location.pathname:!1}function On(e){}const Yt=-1,Gt=-2,Ht=-3,Kt=-4,Wt=-5,Jt=-6,Xt=-7,ct=2**32-1,Ie=ct-1;function zt(e){return!(!Number.isInteger(e)||e<0||e>Ie)}function Zt(e){return!(!Number.isInteger(e)||e<0||e>ct)}function Qt(e){return Uint8Array.fromBase64(e).buffer}function en(e){return Uint8Array.from(Buffer.from(e,"base64")).buffer}function tn(e){const t=atob(e),n=t.length,r=new Uint8Array(n);for(let a=0;a<n;a++)r[a]=t.charCodeAt(a);return r.buffer}const nn=typeof Uint8Array.fromBase64=="function",rn=typeof process=="object"&&process.versions?.node!==void 0,an=nn?Qt:rn?en:tn;function on(e,t){if(typeof e=="number")return i(e,!0);if(!Array.isArray(e)||e.length===0)throw new Error("Invalid input");const n=e,r=Array(n.length);let a=null;function i(o,c=!1){if(o===Yt)return;if(o===Ht)return NaN;if(o===Kt)return 1/0;if(o===Wt)return-1/0;if(o===Jt)return-0;if(c||typeof o!="number")throw new Error("Invalid input");if(o in r)return r[o];const s=n[o];if(!s||typeof s!="object")r[o]=s;else if(Array.isArray(s))if(typeof s[0]=="string"){const l=s[0],f=t&&Object.hasOwn(t,l)?t[l]:void 0;if(f){let d=s[1];if(typeof d!="number"&&(d=n.push(s[1])-1),a??=new Set,a.has(d))throw new Error("Invalid circular reference");return a.add(d),r[o]=f(i(d)),a.delete(d),r[o]}switch(l){case"Date":r[o]=new Date(s[1]);break;case"Set":const d=new Set;r[o]=d;for(let u=1;u<s.length;u+=1)d.add(i(s[u]));break;case"Map":const p=new Map;r[o]=p;for(let u=1;u<s.length;u+=2)p.set(i(s[u]),i(s[u+1]));break;case"RegExp":r[o]=new RegExp(s[1],s[2]);break;case"Object":{const u=s[1];if(typeof n[u]=="object"&&n[u][0]!=="BigInt")throw new Error("Invalid input");r[o]=Object(i(u));break}case"BigInt":r[o]=BigInt(s[1]);break;case"null":const h=Object.create(null);r[o]=h;for(let u=1;u<s.length;u+=2){if(s[u]==="__proto__")throw new Error("Cannot parse an object with a `__proto__` property");h[s[u]]=i(s[u+1])}break;case"Int8Array":case"Uint8Array":case"Uint8ClampedArray":case"Int16Array":case"Uint16Array":case"Float16Array":case"Int32Array":case"Uint32Array":case"Float32Array":case"Float64Array":case"BigInt64Array":case"BigUint64Array":case"DataView":{if(n[s[1]][0]!=="ArrayBuffer")throw new Error("Invalid data");const u=globalThis[l],m=i(s[1]);r[o]=s[2]!==void 0?new u(m,s[2],s[3]):new u(m);break}case"ArrayBuffer":{const u=s[1];if(typeof u!="string")throw new Error("Invalid ArrayBuffer encoding");const m=an(u);r[o]=m;break}case"Temporal.Duration":case"Temporal.Instant":case"Temporal.PlainDate":case"Temporal.PlainTime":case"Temporal.PlainDateTime":case"Temporal.PlainMonthDay":case"Temporal.PlainYearMonth":case"Temporal.ZonedDateTime":{const u=l.slice(9);r[o]=Temporal[u].from(s[1]);break}case"URL":{const u=new URL(s[1]);r[o]=u;break}case"URLSearchParams":{const u=new URLSearchParams(s[1]);r[o]=u;break}default:throw new Error(`Unknown type ${l}`)}}else if(s[0]===Xt){const l=s[1];if(!Zt(l))throw new Error("Invalid input");const f=[];r[o]=f,f[Ie]=void 0,delete f[Ie];for(let d=2;d<s.length;d+=2){const p=s[d];if(!zt(p)||p>=l)throw new Error("Invalid input");f[p]=i(s[d+1])}f.length=l}else{const l=new Array(s.length);r[o]=l;for(let f=0;f<s.length;f+=1){const d=s[f];d!==Gt&&(l[f]=i(d))}}else{const l={};r[o]=l;for(const f of Object.keys(s)){if(f==="__proto__")throw new Error("Cannot parse an object with a `__proto__` property");const d=s[f];l[f]=i(d)}}return r[o]}return i(0)}const lt=new Set(["load","prerender","csr","ssr","trailingSlash","config"]);[...lt];const sn=new Set([...lt]);[...sn];function cn(e){return e.filter(t=>t!=null)}const ln="x-sveltekit-invalidated",fn="x-sveltekit-trailing-slash";function oe(e,t){return e+"/"+t}function le(e){return e instanceof me||e instanceof je?e.status:500}function un(e){return e instanceof je?e.text:"Internal Error"}let I,te,Ae;const dn=Ye.toString().includes("$$")||/function \w+\(\) \{\}/.test(Ye.toString()),We="a:";dn?(I={data:{},form:null,error:null,params:{},route:{id:null},state:{},status:-1,url:new URL(We)},te={current:null},Ae={current:!1}):(I=new class{#e=P({});get data(){return N(this.#e)}set data(t){j(this.#e,t)}#t=P(null);get form(){return N(this.#t)}set form(t){j(this.#t,t)}#n=P(null);get error(){return N(this.#n)}set error(t){j(this.#n,t)}#r=P({});get params(){return N(this.#r)}set params(t){j(this.#r,t)}#a=P({id:null});get route(){return N(this.#a)}set route(t){j(this.#a,t)}#o=P({});get state(){return N(this.#o)}set state(t){j(this.#o,t)}#s=P(-1);get status(){return N(this.#s)}set status(t){j(this.#s,t)}#i=P(new URL(We));get url(){return N(this.#i)}set url(t){j(this.#i,t)}},te=new class{#e=P(null);get current(){return N(this.#e)}set current(t){j(this.#e,t)}},Ae=new class{#e=P(!1);get current(){return N(this.#e)}set current(t){j(this.#e,t)}},it.v=()=>Ae.current=!0);function ft(e){Object.assign(I,e)}const hn="/__data.json",pn=".html__data.json";function _n(e){return e.endsWith(".html")?e.replace(/\.html$/,pn):e.replace(/\/$/,"")+hn}async function*gn(e){let t=!1,n="";const r=new TextDecoder;for(;;){let a=n.indexOf(`
2
+ `);for(;a!==-1;){const o=n.slice(0,a).trim();n=n.slice(a+1),o&&(yield JSON.parse(o)),a=n.indexOf(`
3
+ `)}if(t){const o=n.trim();o&&(yield JSON.parse(o));return}const i=await e.read();t=i.done,i.value&&(n+=r.decode(i.value,{stream:!0})),t&&(n+=r.decode())}}const mn=new Set(["icon","shortcut icon","apple-touch-icon"]);let J=null;const B=tt(rt)??{},ne=tt(nt)??{},D={url:Ke({}),page:Ke({}),navigating:Pe(null),updated:qt()};function Ce(e){B[e]=V()}function wn(e,t){let n=e+1;for(;B[n];)delete B[n],n+=1;for(n=t+1;ne[n];)delete ne[n],n+=1}function K(e,t=!1){return t?location.replace(e.href):location.href=e.href,new Promise($)}async function ut(){if("serviceWorker"in navigator){const e=await navigator.serviceWorker.getRegistration(L||"/");e&&await e.update()}}let $e,Te,fe,x,Ue,b;const ue=[],de=[];let T=null;function he(){T?.fork?.then(e=>e?.discard()),T=null}const se=new Map,dt=new Set,yn=new Set,Z=new Set;let w={branch:[],error:null,url:null},De=!1,pe=!1,Je=!0,re=!1,X=!1,ht=!1,Be=!1,pt,S,U,C;const Q=new Set,Xe=new Map,ze=new Map;async function Dn(e,t,n){globalThis.__sveltekit_1f9h0ew&&(globalThis.__sveltekit_1f9h0ew.query,globalThis.__sveltekit_1f9h0ew.prerender),document.URL!==location.href&&(location.href=location.href),b=e,await e.hooks.init?.(),$e=Mt(e),x=document.documentElement,Ue=t,Te=e.nodes[0],fe=e.nodes[1],Te(),fe(),S=history.state?.[Y],U=history.state?.[ee],S||(S=U=Date.now(),history.replaceState({...history.state,[Y]:S,[ee]:U},""));const r=B[S];function a(){r&&(history.scrollRestoration="manual",scrollTo(r.x,r.y))}n?(a(),await Ln(Ue,n)):(await G({type:"enter",url:xe(b.hash?Nn(new URL(location.href)):location.href),replace_state:!0}),a()),Un()}function vn(){ue.length=0,Be=!1}function _t(e){de.some(t=>t?.snapshot)&&(ne[e]=de.map(t=>t?.snapshot?.capture()))}function gt(e){ne[e]?.forEach((t,n)=>{de[n]?.snapshot?.restore(t)})}function Ze(){Ce(S),Ge(rt,B),_t(U),Ge(nt,ne)}async function mt(e,t,n,r){let a,i;t.invalidateAll&&he(),await G({type:"goto",url:xe(e),keepfocus:t.keepFocus,noscroll:t.noScroll,replace_state:t.replaceState,state:t.state,redirect_count:n,nav_token:r,accept:()=>{if(t.invalidateAll){Be=!0,a=new Set;for(const[o,c]of Xe)for(const s of c.keys())a.add(oe(o,s));i=new Set;for(const[o,c]of ze)for(const s of c.keys())i.add(oe(o,s))}t.invalidate&&t.invalidate.forEach(Tn)}}),t.invalidateAll&&ie().then(ie).then(()=>{for(const[o,c]of Xe)for(const[s,{resource:l}]of c)a?.has(oe(o,s))&&l.refresh();for(const[o,c]of ze)for(const[s,{resource:l}]of c)i?.has(oe(o,s))&&l.reconnect()})}async function bn(e){if(e.id!==T?.id){he();const t={};Q.add(t),T={id:e.id,token:t,promise:yt({...e,preload:t}).then(n=>(Q.delete(t),n.type==="loaded"&&n.state.error&&he(),n)),fork:null}}return T.promise}async function Se(e){const t=(await ve(e,!1))?.route;t&&await Promise.all([...t.layouts,t.leaf].filter(Boolean).map(n=>n[1]()))}async function wt(e,t,n){const r={params:w.params,route:{id:w.route?.id??null},url:new URL(location.href)};w={...e.state,nav:r};const a=document.querySelector("style[data-sveltekit]");if(a&&a.remove(),ft(e.props.page),pt=new b.root({target:t,props:{...e.props,stores:D,components:de},hydrate:n,sync:!1,transformError:void 0}),await Promise.resolve(),gt(U),n){const i={from:null,to:{...r,scroll:B[S]??V()},willUnload:!1,type:"enter",complete:Promise.resolve()};Z.forEach(o=>o(i))}pe=!0}async function _e({url:e,params:t,branch:n,errors:r,status:a,error:i,route:o,form:c}){let s="never";if(L&&(e.pathname===L||e.pathname===L+"/"))s="always";else for(const u of n)u?.slash!==void 0&&(s=u.slash);e.pathname=It(e.pathname,s),e.search=e.search;const l={type:"loaded",state:{url:e,params:t,branch:n,error:i,route:o},props:{constructors:cn(n).map(u=>u.node.component),page:qe(I)}};c!==void 0&&(l.props.form=c);let f={},d=!I,p=0;for(let u=0;u<Math.max(n.length,w.branch.length);u+=1){const m=n[u],k=w.branch[u];m?.data!==k?.data&&(d=!0),m&&(f={...f,...m.data},d&&(l.props[`data_${p}`]=f),p+=1)}return(!w.url||e.href!==w.url.href||w.error!==i||c!==void 0&&c!==I.form||d)&&(l.props.page={error:i,params:t,route:{id:o?.id??null},state:{},status:a,url:new URL(e),form:c??null,data:d?f:I.data}),l}async function Me({loader:e,parent:t,url:n,params:r,route:a,server_data_node:i}){let o=null;const c={dependencies:new Set,params:new Set,parent:!1,route:!1,url:!1,search_params:new Set},s=await e();return{node:s,loader:e,server:i,universal:s.universal?.load?{type:"data",data:o,uses:c}:null,data:o??i?.data??null,slash:s.universal?.trailingSlash??i?.slash}}function kn(e,t,n){let r=e instanceof Request?e.url:e;const a=new URL(r,n);a.origin===n.origin&&(r=a.href.slice(n.origin.length));const i=pe?Ot(r,a.href,t):jt(r,t);return{resolved:a,promise:i}}function Qe(e,t,n,r,a,i){if(Be)return!0;if(!a)return!1;if(a.parent&&e||a.route&&t||a.url&&n)return!0;for(const o of a.search_params)if(r.has(o))return!0;for(const o of a.params)if(i[o]!==w.params[o])return!0;for(const o of a.dependencies)if(ue.some(c=>c(new URL(o))))return!0;return!1}function Fe(e,t){return e?.type==="data"?e:e?.type==="skip"?t??null:null}function En(e,t){if(!e)return new Set(t.searchParams.keys());const n=new Set([...e.searchParams.keys(),...t.searchParams.keys()]);for(const r of n){const a=e.searchParams.getAll(r),i=t.searchParams.getAll(r);a.every(o=>i.includes(o))&&i.every(o=>a.includes(o))&&n.delete(r)}return n}function et({error:e,url:t,route:n,params:r}){return{type:"loaded",state:{error:e,url:t,route:n,params:r,branch:[]},props:{page:qe(I),constructors:[]}}}async function yt({id:e,invalidating:t,url:n,params:r,route:a,preload:i}){if(T?.id===e)return Q.delete(T.token),T.promise;const{errors:o,layouts:c,leaf:s}=a,l=[...c,s];o.forEach(g=>g?.().catch($)),l.forEach(g=>g?.[1]().catch($));let f=null;const d=w.url?e!==ge(w.url):!1,p=w.route?a.id!==w.route.id:!1,h=En(w.url,n);let u=!1;{const g=l.map((y,E)=>{const A=w.branch[E],v=!!y?.[0]&&(A?.loader!==y[1]||Qe(u,p,d,h,A.server?.uses,r));return v&&(u=!0),v});if(g.some(Boolean)){try{f=await kt(n,g)}catch(y){const E=await H(y,{url:n,params:r,route:{id:e}});return Q.has(i)?et({error:E,url:n,params:r,route:a}):ye({status:le(y),error:E,url:n,route:a})}if(f.type==="redirect")return f}}const m=f?.nodes;let k=!1;const _=l.map(async(g,y)=>{if(!g)return;const E=w.branch[y],A=m?.[y];if((!A||A.type==="skip")&&g[1]===E?.loader&&!Qe(k,p,d,h,E.universal?.uses,r))return E;if(k=!0,A?.type==="error")throw A;return Me({loader:g[1],url:n,params:r,route:a,parent:async()=>{const O={};for(let M=0;M<y;M+=1)Object.assign(O,(await _[M])?.data);return O},server_data_node:Fe(A===void 0&&g[0]?{type:"skip"}:A??null,g[0]?E?.server:void 0)})});for(const g of _)g.catch($);const R=[];for(let g=0;g<l.length;g+=1)if(l[g])try{R.push(await _[g])}catch(y){if(y instanceof Ne)return{type:"redirect",location:y.location};if(Q.has(i))return et({error:await H(y,{params:r,url:n,route:{id:a.id}}),url:n,params:r,route:a});let E=le(y),A;if(m?.includes(y))E=y.status??E,A=y.error;else if(y instanceof me)A=y.body;else{if(await D.updated.check())return await ut(),await K(n);A=await H(y,{params:r,url:n,route:{id:a.id}})}const v=await An(g,R,o);return v?_e({url:n,params:r,branch:R.slice(0,v.idx).concat(v.node),errors:o,status:E,error:A,route:a}):await bt(n,{id:a.id},A,E)}else R.push(void 0);return _e({url:n,params:r,branch:R,errors:o,status:200,error:null,route:a,form:t?void 0:null})}async function An(e,t,n){for(;e--;)if(n[e]){let r=e;for(;!t[r];)r-=1;try{return{idx:r+1,node:{node:await n[e](),loader:n[e],data:{},server:null,universal:null}}}catch{continue}}}async function ye({status:e,error:t,url:n,route:r}){const a={};let i=null;if(b.server_loads[0]===0)try{const c=await kt(n,[!0]);if(c.type!=="data"||c.nodes[0]&&c.nodes[0].type!=="data")throw 0;i=c.nodes[0]??null}catch{(n.origin!==ae||n.pathname!==location.pathname||De)&&await K(n)}try{const o=await Me({loader:Te,url:n,params:a,route:r,parent:()=>Promise.resolve({}),server_data_node:Fe(i)}),c={node:await fe(),loader:fe,universal:null,server:null,data:null};return _e({url:n,params:a,branch:[o,c],status:e,error:t,errors:[],route:null})}catch(o){if(o instanceof Ne)return mt(new URL(o.location,location.href),{},0);throw o}}async function Sn(e){const t=e.href;if(se.has(t))return se.get(t);let n;try{const r=(async()=>{let a=await b.hooks.reroute({url:new URL(e),fetch:async(i,o)=>kn(i,o,e).promise})??e;if(typeof a=="string"){const i=new URL(e);b.hash?i.hash=a:i.pathname=a,a=i}return a})();se.set(t,r),n=await r}catch{se.delete(t);return}return n}async function ve(e,t){if(e&&!we(e,L,b.hash)){const n=await Sn(e);if(!n)return;const r=Rn(n);for(const a of $e){const i=a.exec(r);if(i)return{id:ge(e),invalidating:t,route:a,params:Ut(i),url:e}}}}function Rn(e){return Tt(b.hash?e.hash.replace(/^#/,"").replace(/[?#].+/,""):e.pathname.slice(L.length))||"/"}function ge(e){return(b.hash?e.hash.replace(/^#/,""):e.pathname)+e.search}function vt({url:e,type:t,intent:n,delta:r,event:a,scroll:i}){let o=!1;const c=Ve(w,n,e,t,i??null);r!==void 0&&(c.navigation.delta=r),a!==void 0&&(c.navigation.event=a);const s={...c.navigation,cancel:()=>{o=!0,c.reject(new Error("navigation cancelled"))}};return re||dt.forEach(l=>l(s)),o?null:c}async function G({type:e,url:t,popped:n,keepfocus:r,noscroll:a,replace_state:i,state:o={},redirect_count:c=0,nav_token:s={},accept:l=$,block:f=$,event:d}){const p=C;C=s;const h=await ve(t,!1),u=e==="enter"?Ve(w,h,t,e):vt({url:t,type:e,delta:n?.delta,intent:h,scroll:n?.scroll,event:d});if(!u){f(),C===s&&(C=p);return}const m=S,k=U;l(),re=!0,pe&&u.navigation.type!=="enter"&&D.navigating.set(te.current=u.navigation);let _=h&&await yt(h);if(!_){if(we(t,L,b.hash))return await K(t,i);_=await bt(t,{id:null},await H(new je(404,"Not Found",`Not found: ${t.pathname}`),{url:t,params:{},route:{id:null}}),404,i)}if(t=h?.url||t,C!==s)return u.reject(new Error("navigation aborted")),!1;if(_.type==="redirect"){if(c<20){await G({type:e,url:new URL(_.location,t),popped:n,keepfocus:r,noscroll:a,replace_state:i,state:o,redirect_count:c+1,nav_token:s}),u.fulfil(void 0);return}_=await ye({status:500,error:await H(new Error("Redirect loop"),{url:t,params:{},route:{id:null}}),url:t,route:{id:null}})}else _.props.page.status>=400&&await D.updated.check()&&(await ut(),await K(t,i));if(vn(),Ce(m),_t(k),_.props.page.url.pathname!==t.pathname&&(t.pathname=_.props.page.url.pathname),o=n?n.state:o,!n){const v=i?0:1,O={[Y]:S+=v,[ee]:U+=v,[at]:o};(i?history.replaceState:history.pushState).call(history,O,"",t),i||wn(S,U)}const R=h&&T?.id===h.id?T.fork:null;T?.fork&&!R&&he(),T=null,_.props.page.state=o;let g;if(pe){const v=(await Promise.all(Array.from(yn,W=>W(u.navigation)))).filter(W=>typeof W=="function");if(v.length>0){let W=function(){v.forEach(be=>{Z.delete(be)})};v.push(W),v.forEach(be=>{Z.add(be)})}const O=u.navigation.to;w={..._.state,nav:{params:O.params,route:O.route,url:O.url}},_.props.page&&(_.props.page.url=t);const M=R&&await R;M?g=M.commit():(J=null,pt.$set(_.props),J&&Object.assign(_.props.page,J),ft(_.props.page),g=St?.()),ht=!0}else await wt(_,Ue,!1);const{activeElement:y}=document;if(await g,await ie(),await ie(),C!==s)return u.reject(new Error("navigation aborted")),!1;_.props.page&&J&&Object.assign(_.props.page,J);let E=null;if(Je){const v=n?n.scroll:a?V():null;v?scrollTo(v.x,v.y):(E=t.hash&&document.getElementById(At(t)))?E.scrollIntoView():scrollTo(0,0)}const A=document.activeElement!==y&&document.activeElement!==document.body;!r&&!A&&Pn(t,!E),Je=!0,re=!1,e==="popstate"&&gt(U),u.fulfil(void 0),u.navigation.to&&(u.navigation.to.scroll=V()),Z.forEach(v=>v(u.navigation)),D.navigating.set(te.current=null)}async function bt(e,t,n,r,a){return e.origin===ae&&e.pathname===location.pathname&&!De?await ye({status:r,error:n,url:e,route:t}):await K(e,a)}function In(){let e,t={element:void 0,href:void 0},n;x.addEventListener("mousemove",c=>{const s=c.target;clearTimeout(e),e=setTimeout(()=>{i(s,F.hover)},20)});function r(c){c.defaultPrevented||i(c.composedPath()[0],F.tap)}x.addEventListener("mousedown",r),x.addEventListener("touchstart",r,{passive:!0});const a=new IntersectionObserver(c=>{for(const s of c)s.isIntersecting&&(Se(new URL(s.target.href)),a.unobserve(s.target))},{threshold:0});async function i(c,s){const l=st(c,x),f=l===t.element&&l?.href===t.href&&s>=n;if(!l||f)return;const{url:d,external:p,download:h}=Re(l,L,b.hash);if(p||h)return;const u=ce(l),m=d&&ge(w.url)===ge(d);if(!(u.reload||m))if(s<=u.preload_data){t={element:l,href:l.href},n=F.tap;const k=await ve(d,!1);if(!k)return;bn(k)}else s<=u.preload_code&&(t={element:l,href:l.href},n=s,Se(d))}function o(){a.disconnect();for(const c of x.querySelectorAll("a")){const{url:s,external:l,download:f}=Re(c,L,b.hash);if(l||f)continue;const d=ce(c);d.reload||(d.preload_code===F.viewport&&a.observe(c),d.preload_code===F.eager&&Se(s))}}Z.add(o),o()}function H(e,t){if(e instanceof me)return e.body;const n=le(e),r=un(e);return b.hooks.handleError({error:e,event:t,status:n,message:r})??{message:r}}function Bn(e,t={}){return e=new URL(xe(e)),e.origin!==ae?Promise.reject(new Error("goto: invalid URL")):mt(e,t,0)}function Tn(e){if(typeof e=="function")ue.push(e);else{const{href:t}=new URL(e,location.href);ue.push(n=>n.href===t)}}function Un(){history.scrollRestoration="manual",addEventListener("beforeunload",t=>{let n=!1;if(Ze(),!re){const r=Ve(w,void 0,null,"leave"),a={...r.navigation,cancel:()=>{n=!0,r.reject(new Error("navigation cancelled"))}};dt.forEach(i=>i(a))}n?(t.preventDefault(),t.returnValue=""):history.scrollRestoration="auto"}),addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&Ze()}),navigator.connection?.saveData||In(),x.addEventListener("click",async t=>{if(t.button||t.which!==1||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||t.defaultPrevented)return;const n=st(t.composedPath()[0],x);if(!n)return;const{url:r,external:a,target:i,download:o}=Re(n,L,b.hash);if(!r)return;if(i==="_parent"||i==="_top"){if(window.parent!==window)return}else if(i&&i!=="_self")return;const c=ce(n);if(!(n instanceof SVGAElement)&&r.protocol!==location.protocol&&!(r.protocol==="https:"||r.protocol==="http:")||o)return;const[l,f]=(b.hash?r.hash.replace(/^#/,""):r.href).split("#"),d=l===ke(location);if(a||c.reload&&(!d||!f)){vt({url:r,type:"link",event:t})?re=!0:t.preventDefault();return}if(f!==void 0&&d){const[,p]=w.url.href.split("#");if(p===f){if(t.preventDefault(),f===""||f==="top"&&n.ownerDocument.getElementById("top")===null)scrollTo({top:0});else{const h=n.ownerDocument.getElementById(decodeURIComponent(f));h&&(h.scrollIntoView(),h.focus())}return}if(X=!0,Ce(S),e(r),!c.replace_state)return;X=!1}t.preventDefault(),await new Promise(p=>{requestAnimationFrame(()=>{setTimeout(p,0)}),setTimeout(p,100)}),await G({type:"link",url:r,keepfocus:c.keepfocus,noscroll:c.noscroll,replace_state:c.replace_state??r.href===location.href,event:t})}),x.addEventListener("submit",t=>{if(t.defaultPrevented)return;const n=HTMLFormElement.prototype.cloneNode.call(t.target),r=t.submitter;if((r?.formTarget||n.target)==="_blank"||(r?.formMethod||n.method)!=="get")return;const o=new URL(r?.hasAttribute("formaction")&&r?.formAction||n.action);if(we(o,L,!1))return;const c=t.target,s=ce(c);if(s.reload)return;t.preventDefault(),t.stopPropagation();const l=new FormData(c,r);o.search=new URLSearchParams(l).toString(),G({type:"form",url:o,keepfocus:s.keepfocus,noscroll:s.noscroll,replace_state:s.replace_state??o.href===location.href,event:t})}),addEventListener("popstate",async t=>{if(!Le){if(t.state?.[Y]){const n=t.state[Y];if(C={},n===S)return;const r=B[n],a=t.state[at]??{},i=new URL(t.state[Vt]??location.href),o=t.state[ee],c=w.url?ke(location)===ke(w.url):!1;if(o===U&&(ht||c)){a!==I.state&&(I.state=a),e(i),B[S]=V(),r&&scrollTo(r.x,r.y),S=n;return}const l=n-S;await G({type:"popstate",url:i,popped:{state:a,scroll:r,delta:l},accept:()=>{S=n,U=o},block:()=>{history.go(-l)},nav_token:C,event:t})}else if(!X){const n=new URL(location.href);e(n),b.hash&&location.reload()}}}),addEventListener("hashchange",()=>{X&&(X=!1,history.replaceState({...history.state,[Y]:++S,[ee]:U},"",location.href))});for(const t of document.querySelectorAll("link"))mn.has(t.rel)&&(t.href=t.href);addEventListener("pageshow",t=>{t.persisted&&D.navigating.set(te.current=null)});function e(t){w.url=I.url=t,D.page.set(qe(I)),D.page.notify()}}async function Ln(e,{status:t=200,error:n,node_ids:r,params:a,route:i,server_route:o,data:c,form:s}){De=!0;const l=new URL(location.href);let f;({params:a={},route:i={id:null}}=await ve(l,!1)||{}),f=$e.find(({id:h})=>h===i.id);let d,p=!0;try{const h=r.map(async(m,k)=>{const _=c[k];return _?.uses&&(_.uses=Et(_.uses)),Me({loader:b.nodes[m],url:l,params:a,route:i,parent:async()=>{const R={};for(let g=0;g<k;g+=1)Object.assign(R,(await h[g]).data);return R},server_data_node:Fe(_)})}),u=await Promise.all(h);if(f){const m=f.layouts;for(let k=0;k<m.length;k++)m[k]||u.splice(k,0,void 0)}d=await _e({url:l,params:a,branch:u,status:t,error:n,errors:f?.errors,form:s,route:f??null})}catch(h){if(h instanceof Ne){await K(new URL(h.location,location.href));return}d=await ye({status:le(h),error:await H(h,{url:l,params:a,route:i}),url:l,route:i}),e.textContent="",p=!1}d.props.page&&(d.props.page.state={}),await wt(d,e,p)}async function kt(e,t){const n=new URL(e);n.pathname=_n(e.pathname),e.pathname.endsWith("/")&&n.searchParams.append(fn,"1"),n.searchParams.append(ln,t.map(i=>i?"1":"0").join(""));const r=window.fetch,a=await r(n.href,{});if(!a.ok){let i;throw a.headers.get("content-type")?.includes("application/json")?i=await a.json():a.status===404?i="Not Found":a.status===500&&(i="Internal Error"),new me(a.status,i)}return new Promise(async i=>{const o=new Map,c=a.body.getReader();function s(l){return on(l,{...b.decoders,Promise:f=>new Promise((d,p)=>{o.set(f,{fulfil:d,reject:p})})})}for await(const l of gn(c)){if(l.type==="redirect")return i(l);if(l.type==="data")l.nodes?.forEach(f=>{f?.type==="data"&&(f.uses=Et(f.uses),f.data=s(f.data))}),i(l);else if(l.type==="chunk"){const{id:f,data:d,error:p}=l,h=o.get(f);o.delete(f),p?h.reject(s(p)):h.fulfil(s(d))}}})}function Et(e){return{dependencies:new Set(e?.dependencies??[]),params:new Set(e?.params??[]),parent:!!e?.parent,route:!!e?.route,url:!!e?.url,search_params:new Set(e?.search_params??[])}}let Le=!1;function Pn(e,t=!0){const n=document.querySelector("[autofocus]");if(n)n.focus();else{const r=At(e);if(r&&document.getElementById(r)){const{x:i,y:o}=V();setTimeout(()=>{const c=history.state;Le=!0,location.replace(new URL(`#${r}`,location.href)),history.replaceState(c,"",e),t&&scrollTo(i,o),Le=!1})}else{const i=document.body,o=i.getAttribute("tabindex");i.tabIndex=-1,i.focus({preventScroll:!0,focusVisible:!1}),o!==null?i.setAttribute("tabindex",o):i.removeAttribute("tabindex")}const a=getSelection();if(a&&a.type!=="None"){const i=[];for(let o=0;o<a.rangeCount;o+=1)i.push(a.getRangeAt(o));setTimeout(()=>{if(a.rangeCount===i.length){for(let o=0;o<a.rangeCount;o+=1){const c=i[o],s=a.getRangeAt(o);if(c.commonAncestorContainer!==s.commonAncestorContainer||c.startContainer!==s.startContainer||c.endContainer!==s.endContainer||c.startOffset!==s.startOffset||c.endOffset!==s.endOffset)return}a.removeAllRanges()}})}}}function Ve(e,t,n,r,a=null){let i,o;const c=new Promise((l,f)=>{i=l,o=f});return c.catch($),{navigation:{from:{params:e.params,route:{id:e.route?.id??null},url:e.url,scroll:V()},to:n&&{params:t?.params??null,route:{id:t?.route?.id??null},url:n,scroll:a},willUnload:!t,type:r,complete:c},fulfil:i,reject:o}}function qe(e){return{data:e.data,error:e.error,form:e.form,params:e.params,route:e.route,state:e.state,status:e.status,url:e.url}}function Nn(e){const t=new URL(e);return t.hash=decodeURIComponent(e.hash),t}function At(e){let t;if(b.hash){const[,,n]=e.hash.split("#",3);t=n??""}else t=e.hash.slice(1);return decodeURIComponent(t)}export{Dn as a,Bn as g,On as l,I as p,D as s};