@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
@@ -1,17 +1,17 @@
1
1
  <script lang="ts">
2
- import type { ConfigPageData, NativeBridgeConfig, ShooterConfig } from '$lib/types';
2
+ import type {
3
+ ConfigPageData,
4
+ DeviceListItem,
5
+ NativeBridgeConfig,
6
+ ShooterConfig,
7
+ } from '$lib/types';
3
8
 
4
9
  import { browser } from '$app/environment';
5
10
  import AlertTriangleSvg from '$lib/assets/icons/alert-triangle.svg?raw';
6
11
  import CheckCircleSvg from '$lib/assets/icons/check-circle.svg?raw';
7
12
  import PlaySvg from '$lib/assets/icons/play.svg?raw';
8
13
  import XCircleSvg from '$lib/assets/icons/x-circle.svg?raw';
9
- import {
10
- hasScanner,
11
- isShooterConfig,
12
- scanQR,
13
- toErrorMessage,
14
- } from '$lib/modules/client/common';
14
+ import { hasScanner, isShooterConfig, scanQR, toErrorMessage } from '$lib/modules/client/common';
15
15
  import { PROVIDERS } from '$lib/modules/client/neurolink/provider-config';
16
16
  import { Banner, Button, Card, Icon, Input, Stepper } from '@juspay/svelte-ui-components';
17
17
  import { onMount } from 'svelte';
@@ -20,11 +20,13 @@
20
20
 
21
21
  let serverUrl = $state('');
22
22
  let apiKey = $state('');
23
- let deviceToken = $state('');
23
+ let registeredDevices = $state<DeviceListItem[]>([]);
24
+ let loadingDevices = $state(false);
25
+ let loadDevicesError = $state('');
26
+ let thisDeviceId = $state('');
24
27
  let result = $state('');
25
28
  let loading = $state(false);
26
29
  let statusType = $state<'' | 'error' | 'success' | 'warning'>('');
27
- let isNativeApp = $state(false);
28
30
  let qrDataUrl = $state('');
29
31
  let qrServerUrl = $state('');
30
32
  let qrLoading = $state(false);
@@ -130,7 +132,6 @@
130
132
  function hydrateBridge(): void {
131
133
  const bridge = getNativeBridge();
132
134
  if (bridge && !bridgeHydrated) {
133
- isNativeApp = true;
134
135
  try {
135
136
  const nativeConfig = JSON.parse(
136
137
  bridge.getConfig?.() ?? '{}'
@@ -144,10 +145,12 @@
144
145
  if (nativeConfig.apiKey) {
145
146
  apiKey = nativeConfig.apiKey;
146
147
  }
147
- // Auto-populate device token with FCM token
148
- const fcmToken = bridge.getFcmToken?.();
149
- if (fcmToken) {
150
- deviceToken = fcmToken;
148
+ // Capture this device's stable id so the registered-devices list can
149
+ // highlight "this device". The app registers its own push token
150
+ // directly via /api/device-token — no manual token entry here.
151
+ const bridgeDeviceId = bridge.getDeviceId?.();
152
+ if (bridgeDeviceId) {
153
+ thisDeviceId = bridgeDeviceId;
151
154
  }
152
155
  // Mark hydrated only after native reads succeeded
153
156
  bridgeHydrated = true;
@@ -187,12 +190,17 @@
187
190
  if (parsed.apiKey) {
188
191
  apiKey = parsed.apiKey;
189
192
  }
190
- if (parsed.deviceToken) {
191
- deviceToken = parsed.deviceToken;
192
- }
193
193
  if (parsed.serverUrl) {
194
194
  serverUrl = parsed.serverUrl;
195
195
  }
196
+ // Migration: drop the legacy single deviceToken from stored config —
197
+ // devices are now managed server-side via the registry.
198
+ if (parsed.deviceToken) {
199
+ localStorage.setItem(
200
+ 'shooter_config',
201
+ JSON.stringify({ ...parsed, deviceToken: null } satisfies ShooterConfig)
202
+ );
203
+ }
196
204
  } else {
197
205
  localStorage.removeItem('shooter_config');
198
206
  }
@@ -210,6 +218,7 @@
210
218
  serverUrl = getDefaultServerUrl();
211
219
  }
212
220
  _bridgeCheckDone = true;
221
+ void loadDevices();
213
222
 
214
223
  // The native bridge may be injected after SvelteKit hydration.
215
224
  // Re-check periodically for a short window to catch late injection.
@@ -221,11 +230,19 @@
221
230
  }
222
231
  }, 200);
223
232
  // Stop checking after 3 seconds
224
- setTimeout(() => {
233
+ const recheckTimeout = setTimeout(() => {
225
234
  clearInterval(recheckInterval);
226
235
  }, 3000);
236
+
237
+ // Cancel both timers if the component unmounts before they fire,
238
+ // so hydrateBridge() never writes to a destroyed component.
239
+ return (): void => {
240
+ clearInterval(recheckInterval);
241
+ clearTimeout(recheckTimeout);
242
+ };
227
243
  }
228
244
  }
245
+ return undefined;
229
246
  });
230
247
 
231
248
  async function saveConfiguration(): Promise<void> {
@@ -239,7 +256,7 @@
239
256
  'shooter_config',
240
257
  JSON.stringify({
241
258
  apiKey: apiKey.trim(),
242
- deviceToken: deviceToken.trim(),
259
+ deviceToken: null,
243
260
  lastUpdated: Date.now(),
244
261
  serverUrl: trimmedUrl || getDefaultServerUrl(),
245
262
  } satisfies ShooterConfig)
@@ -264,6 +281,8 @@
264
281
  result = 'Configuration saved but system health check failed';
265
282
  statusType = 'warning';
266
283
  }
284
+
285
+ void loadDevices();
267
286
  } catch (error) {
268
287
  result = `Configuration failed: ${toErrorMessage(error)}`;
269
288
  statusType = 'error';
@@ -284,21 +303,14 @@
284
303
  statusType = '';
285
304
 
286
305
  try {
287
- const testPayload: {
288
- data: Record<string, unknown>;
289
- deviceToken?: string;
290
- message: string;
291
- title: string;
292
- } = {
306
+ // No deviceToken override — the test now fans out to every registered
307
+ // device, exactly like a real notification.
308
+ const testPayload = {
293
309
  data: { source: 'config-test', timestamp: Date.now() },
294
310
  message: `Configuration test at ${new Date().toLocaleTimeString()}`,
295
311
  title: 'Configuration Test',
296
312
  };
297
313
 
298
- if (deviceToken.trim()) {
299
- testPayload.deviceToken = deviceToken.trim();
300
- }
301
-
302
314
  const response = await fetch('/api/notify', {
303
315
  body: JSON.stringify(testPayload),
304
316
  headers: {
@@ -330,7 +342,7 @@
330
342
  localStorage.removeItem('shooter_config');
331
343
  serverUrl = typeof window !== 'undefined' ? window.location.origin : '';
332
344
  apiKey = '';
333
- deviceToken = '';
345
+ registeredDevices = [];
334
346
  result = 'Configuration cleared';
335
347
  statusType = 'success';
336
348
 
@@ -340,6 +352,62 @@
340
352
  }
341
353
  }
342
354
  }
355
+
356
+ /** Load the registered-devices list from the server registry. */
357
+ async function loadDevices(): Promise<void> {
358
+ if (!apiKey.trim()) {
359
+ registeredDevices = [];
360
+ loadDevicesError = '';
361
+ return;
362
+ }
363
+ loadingDevices = true;
364
+ try {
365
+ const response = await fetch('/api/device-token', {
366
+ headers: { Authorization: `Bearer ${apiKey.trim()}` },
367
+ });
368
+ if (response.ok) {
369
+ const data = (await response.json()) as { devices?: DeviceListItem[] };
370
+ registeredDevices = Array.isArray(data.devices) ? data.devices : [];
371
+ loadDevicesError = '';
372
+ } else {
373
+ // Distinguish a load failure from a genuinely empty list — otherwise a
374
+ // 401 (wrong key) shows the misleading "No devices registered yet".
375
+ registeredDevices = [];
376
+ loadDevicesError =
377
+ response.status === 401
378
+ ? 'Could not load devices: the API key was rejected (401). Check the key above.'
379
+ : `Could not load devices (HTTP ${response.status}).`;
380
+ }
381
+ } catch (error) {
382
+ registeredDevices = [];
383
+ loadDevicesError = `Could not load devices: ${toErrorMessage(error)}`;
384
+ }
385
+ loadingDevices = false;
386
+ }
387
+
388
+ /** Remove a device from the registry, then refresh the list. */
389
+ async function removeDevice(id: string): Promise<void> {
390
+ result = '';
391
+ statusType = '';
392
+ try {
393
+ const response = await fetch('/api/device-token', {
394
+ body: JSON.stringify({ id }),
395
+ headers: { Authorization: `Bearer ${apiKey.trim()}`, 'Content-Type': 'application/json' },
396
+ method: 'DELETE',
397
+ });
398
+ if (!response.ok) {
399
+ const data = (await response.json().catch(() => ({}))) as { error?: string };
400
+ result = `Failed to remove device: ${data.error ?? response.statusText}`;
401
+ statusType = 'error';
402
+ return;
403
+ }
404
+ } catch (error) {
405
+ result = `Failed to remove device: ${toErrorMessage(error)}`;
406
+ statusType = 'error';
407
+ return;
408
+ }
409
+ await loadDevices();
410
+ }
343
411
  </script>
344
412
 
345
413
  <svelte:head>
@@ -384,19 +452,42 @@
384
452
  Find this in your <code>~/.shooter/.env</code> file. Run <code>shooter setup</code> to generate
385
453
  one.
386
454
  </p>
455
+ </Card>
387
456
 
388
- <Input
389
- name="deviceToken"
390
- label="Device Token"
391
- bind:value={deviceToken}
392
- dataType="text"
393
- placeholder={isNativeApp ? 'Waiting for token...' : '64-character hex string'}
394
- infoMessage={isNativeApp && deviceToken
395
- ? 'Auto-detected from app'
396
- : 'Device token from app registration'}
397
- classes="input-mono"
398
- />
399
- <p class="input-help">Optional — only needed for iOS/Android push notifications.</p>
457
+ <Card title="Registered Devices" description="Phones that receive push notifications">
458
+ {#if loadingDevices}
459
+ <p class="input-help">Loading…</p>
460
+ {:else if loadDevicesError}
461
+ <p class="input-help" style="color: var(--color-error, #f87171);">{loadDevicesError}</p>
462
+ {:else if registeredDevices.length === 0}
463
+ <p class="input-help">
464
+ No devices registered yet. Open the Shooter app on your phone with this server's URL +
465
+ API key it registers automatically. Every notification fans out to all devices here.
466
+ </p>
467
+ {:else}
468
+ <ul class="device-list">
469
+ {#each registeredDevices as device (device.id)}
470
+ <li class="device-row">
471
+ <div class="device-meta">
472
+ <span class="device-name">
473
+ {device.friendlyName || device.deviceId || 'Unknown device'}
474
+ {#if device.deviceId && device.deviceId === thisDeviceId}
475
+ <span class="device-badge">this device</span>
476
+ {/if}
477
+ </span>
478
+ <span class="device-sub"
479
+ >{device.platform} · {device.appEnv} · {device.tokenMasked}</span
480
+ >
481
+ </div>
482
+ <Button
483
+ classes="btn-secondary"
484
+ onclick={(): void => void removeDevice(device.id)}
485
+ text="Remove"
486
+ />
487
+ </li>
488
+ {/each}
489
+ </ul>
490
+ {/if}
400
491
  </Card>
401
492
 
402
493
  {#if result}
@@ -439,10 +530,14 @@
439
530
  <Stepper
440
531
  steps={[
441
532
  { label: 'Get API Key' },
442
- { label: 'Find Device Token' },
533
+ { label: 'Register a Device' },
443
534
  { label: 'Test Connection' },
444
535
  ]}
445
- currentStepIndex={apiKey.trim() && deviceToken.trim() ? 2 : apiKey.trim() ? 1 : 0}
536
+ currentStepIndex={apiKey.trim() && registeredDevices.length > 0
537
+ ? 2
538
+ : apiKey.trim()
539
+ ? 1
540
+ : 0}
446
541
  classes="setup-stepper"
447
542
  />
448
543
  </Card>
@@ -735,6 +830,49 @@
735
830
  font-size: var(--text-xs);
736
831
  }
737
832
 
833
+ .device-list {
834
+ list-style: none;
835
+ margin: 0;
836
+ padding: 0;
837
+ display: flex;
838
+ flex-direction: column;
839
+ gap: var(--space-2);
840
+ }
841
+ .device-row {
842
+ display: flex;
843
+ align-items: center;
844
+ justify-content: space-between;
845
+ gap: var(--space-3);
846
+ padding: var(--space-2) 0;
847
+ border-bottom: 1px solid var(--ds-gray-200, #1a1a1a);
848
+ }
849
+ .device-meta {
850
+ display: flex;
851
+ flex-direction: column;
852
+ gap: 2px;
853
+ min-width: 0;
854
+ }
855
+ .device-name {
856
+ font-size: var(--text-sm);
857
+ font-weight: 600;
858
+ display: flex;
859
+ align-items: center;
860
+ gap: var(--space-2);
861
+ }
862
+ .device-badge {
863
+ font-size: var(--text-xs);
864
+ font-weight: 500;
865
+ color: var(--ds-green-900, #16a34a);
866
+ border: 1px solid var(--ds-green-700, #16a34a);
867
+ border-radius: var(--radius-sm);
868
+ padding: 0 6px;
869
+ }
870
+ .device-sub {
871
+ font-size: var(--text-xs);
872
+ color: var(--text-tertiary);
873
+ font-family: var(--font-mono);
874
+ }
875
+
738
876
  @media (max-width: 768px) {
739
877
  .settings-grid {
740
878
  grid-template-columns: 1fr;
@@ -1,3 +0,0 @@
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="1781949428296";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_you0vx?.base??"",Ft=globalThis.__sveltekit_you0vx?.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||ye(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 ye(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 yn(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,wn=new Set,Z=new Set;let y={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_you0vx&&(globalThis.__sveltekit_you0vx.query,globalThis.__sveltekit_you0vx.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:wt({...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 yt(e,t,n){const r={params:y.params,route:{id:y.route?.id??null},url:new URL(location.href)};y={...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,y.branch.length);u+=1){const m=n[u],k=y.branch[u];m?.data!==k?.data&&(d=!0),m&&(f={...f,...m.data},d&&(l.props[`data_${p}`]=f),p+=1)}return(!y.url||e.href!==y.url.href||y.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]!==y.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 wt({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=y.url?e!==ge(y.url):!1,p=y.route?a.id!==y.route.id:!1,h=En(y.url,n);let u=!1;{const g=l.map((w,E)=>{const A=y.branch[E],v=!!w?.[0]&&(A?.loader!==w[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(w){const E=await H(w,{url:n,params:r,route:{id:e}});return Q.has(i)?et({error:E,url:n,params:r,route:a}):we({status:le(w),error:E,url:n,route:a})}if(f.type==="redirect")return f}}const m=f?.nodes;let k=!1;const _=l.map(async(g,w)=>{if(!g)return;const E=y.branch[w],A=m?.[w];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<w;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(w){if(w instanceof Ne)return{type:"redirect",location:w.location};if(Q.has(i))return et({error:await H(w,{params:r,url:n,route:{id:a.id}}),url:n,params:r,route:a});let E=le(w),A;if(m?.includes(w))E=w.status??E,A=w.error;else if(w instanceof me)A=w.body;else{if(await D.updated.check())return await ut(),await K(n);A=await H(w,{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 we({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&&!ye(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(y,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(y,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 wt(h);if(!_){if(ye(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 we({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||yn(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(wn,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;y={..._.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 yt(_,Ue,!1);const{activeElement:w}=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!==w&&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 we({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(y.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(y,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]=y.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(ye(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=y.url?ke(location)===ke(y.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){y.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 we({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 yt(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};
@@ -1 +0,0 @@
1
- import{l as o,a as r}from"../chunks/BzW0vlVX.js";export{o as load_css,r as start};
@@ -1,2 +0,0 @@
1
- )�import{l as o,a as r}from"../chunks/BzW0vlVX.js";export{o as load_css,r as start};
2
- 
@@ -1,16 +0,0 @@
1
- import{a as d,f as u,i as K,s as O}from"../chunks/DoGIJDSZ.js";import{c as v,g as e,r as c,t as U,p as be,w as Ce,d as g,v as De,D as Oe,A as J,b as t,x,s as b,$ as Le,B as we}from"../chunks/kzeZOqU5.js";import{e as xe,k as je,s as W,i as $e,a as Je,B as F,I as ie,c as se,b as Me,d as Ee}from"../chunks/aiM7Bbhi.js";import{h as Fe}from"../chunks/BQJR1fvD.js";import{A as Ge}from"../chunks/DzuS5Nbr.js";import{i as ze}from"../chunks/gQJcRhou.js";import"../chunks/Dk1NUsIU.js";import{P as Ve}from"../chunks/C87ZRWX0.js";var He=u("<div><!></div>"),We=u("<div></div>");function Xe(h,o){var f=We();xe(f,21,()=>o.steps,$e,(s,w,l)=>{var S=He();let i;var I=v(S);je(I,{get onclick(){return o.onhandleStepClick},get label(){return e(w).label},get icon(){return e(w).icon},stepIndex:l+1}),c(S),U(()=>i=W(S,1,"step-container svelte-dvxh34",null,i,{"active-step":o.currentStepIndex===l,"completed-step":o.currentStepIndex>l})),d(s,S)}),c(f),U(()=>W(f,1,`container ${o.classes??""??""}`,"svelte-dvxh34")),d(h,f)}var Ye=u('<div class="card-description svelte-15b0v7e"> </div>'),Ze=u('<div class="card-header svelte-15b0v7e"><div class="card-title svelte-15b0v7e"> </div> <!></div>'),et=u('<div><!> <div class="card-content svelte-15b0v7e"><!></div></div>');function G(h,o){be(o,!0);var f=et(),s=v(f);{var w=i=>{var I=Ze(),T=v(I),L=v(T,!0);c(T);var j=g(T,2);{var q=A=>{var M=Ye(),z=v(M,!0);c(M),U(()=>O(z,o.description)),d(A,M)};K(j,A=>{typeof o.description=="string"&&o.description.length>0&&A(q)})}c(I),U(()=>O(L,o.title)),d(i,I)};K(s,i=>{typeof o.title=="string"&&o.title.length>0&&i(w)})}var l=g(s,2),S=v(l);Je(S,()=>o.children),c(l),c(f),U(()=>W(f,1,`card ${o.classes??""??""}`,"svelte-15b0v7e")),d(h,f),Ce()}function H(h){return h instanceof Error?h.message:typeof h=="string"?h:String(h)}function tt(){return Pe()!==null}async function rt(){const h=Pe();if(!h)throw new Error("Scanner not available");return h()}function Pe(){if(typeof window>"u")return null;const h=window.ShooterBridge?.scanner;if(h&&typeof h.scan=="function")return()=>h.scan();const o=window.ShooterNativeBridge?.scanner;return o&&typeof o.scan=="function"?()=>o.scan():null}const Se=`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
- <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
3
- <polyline points="22 4 12 14.01 9 11.01"/>
4
- </svg>
5
- `,nt=`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
6
- <polygon points="5 3 19 12 5 21 5 3"/>
7
- </svg>
8
- `,ke=`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
9
- <circle cx="12" cy="12" r="10"/>
10
- <line x1="15" y1="9" x2="9" y2="15"/>
11
- <line x1="9" y1="9" x2="15" y2="15"/>
12
- </svg>
13
- `;var at=u('<meta name="description" content="Configure notification system settings"/>'),ot=u(`<!> <!> <p class="input-help svelte-1gp6n77">Find this in your <code class="svelte-1gp6n77">~/.shooter/.env</code> file. Run <code class="svelte-1gp6n77">shooter setup</code> to generate
14
- one.</p> <!> <p class="input-help svelte-1gp6n77">Optional — only needed for iOS/Android push notifications.</p>`,1),it=u("<!> Test Connection",1),st=u(`<p class="qr-description svelte-1gp6n77">Scan the QR code shown on your server's settings page to auto-configure the
15
- connection.</p> <!>`,1),ct=u('<p class="qr-server-url svelte-1gp6n77">Server: <code class="svelte-1gp6n77"> </code></p>'),lt=u('<div class="qr-container svelte-1gp6n77"><img alt="QR code for mobile app pairing" class="qr-image svelte-1gp6n77"/></div> <p class="qr-hint svelte-1gp6n77">Scan this QR code with the Shooter iOS or Android app to connect.</p> <!>',1),dt=u('<div class="qr-placeholder svelte-1gp6n77"><p class="qr-loading-text svelte-1gp6n77">Generating QR code...</p></div>'),vt=u('<p class="qr-error svelte-1gp6n77"> </p>'),ut=u(`<p class="qr-description svelte-1gp6n77">Generate a QR code containing your server URL and API key. Your mobile app can
16
- scan it to auto-configure the connection.</p>`),gt=u("<!> <!>",1),ft=u('<div class="qr-section svelte-1gp6n77"><!></div>'),pt=u('<div class="provider-row svelte-1gp6n77"><!> <span class="provider-label svelte-1gp6n77"> </span> <span> </span></div>'),ht=u('<div class="active-provider svelte-1gp6n77">Active: <strong> </strong></div>'),mt=u('<div class="active-provider svelte-1gp6n77">Auto-detected from configured keys</div>'),yt=u('<p class="ai-help svelte-1gp6n77">Run <code class="svelte-1gp6n77">shooter setup</code> to configure AI providers for summaries.</p>'),_t=u('<div class="ai-providers svelte-1gp6n77"><!> <!></div>'),wt=u('<p class="danger-description svelte-1gp6n77">Clear all saved configuration data from this device.</p> <!>',1),St=u('<main class="main"><div class="settings-container svelte-1gp6n77"><div style="margin-bottom: var(--space-4);"><a href="/" class="back-link">← Back to Projects</a></div> <div class="page-header"><h1 class="page-title">Settings</h1> <p class="page-description">Configure your API credentials and notification preferences</p></div> <div class="settings-grid svelte-1gp6n77"><section class="settings-section svelte-1gp6n77"><!> <!> <div class="button-group svelte-1gp6n77"><!> <!></div></section> <aside class="settings-sidebar svelte-1gp6n77"><!> <!> <!> <!></aside></div></div></main>');function Tt(h,o){be(o,!0);let f=b(""),s=b(""),w=b(""),l=b(""),S=b(!1),i=b(""),I=b(!1),T=b(""),L=b(""),j=b(!1),q=b(""),A=b(!1),M=b(!1),z=!1,V=b(!1);async function Re(){if(!e(s).trim()){t(q,"Save an API key first to generate a QR code");return}t(j,!0),t(q,""),t(T,""),t(L,"");try{const r=await fetch("/api/qr-config",{headers:{Authorization:`Bearer ${e(s).trim()}`}});if(!r.ok){const a=await r.json();t(q,a.error||"Failed to generate QR code",!0);return}const n=await r.json();t(T,n.dataUrl,!0),t(L,n.serverUrl,!0)}catch(r){t(q,`Network error: ${H(r)}`)}finally{t(j,!1)}}async function Ue(){t(V,!0),t(l,""),t(i,"");try{const r=await rt();try{const n=JSON.parse(r);typeof n.serverUrl=="string"&&n.serverUrl&&t(f,n.serverUrl,!0),typeof n.apiKey=="string"&&n.apiKey&&t(s,n.apiKey,!0),await Z();const a=e(i);a!=="error"&&a!=="warning"&&(t(l,"Configuration updated from QR code"),t(i,"success"))}catch{t(l,"Invalid QR code data"),t(i,"error")}}catch(r){const n=H(r);n!=="cancelled"&&(t(l,`Scanner error: ${n}`),t(i,"error"))}finally{t(V,!1)}}function X(){return typeof window>"u"?null:window.ShooterBridge?window.ShooterBridge:window.ShooterNativeBridge?window.ShooterNativeBridge:null}function Y(){return window.location.origin}function ce(){const r=X();if(r&&!z){t(I,!0);try{const n=JSON.parse(r.getConfig?.()??"{}");n.serverUrl&&t(f,n.serverUrl,!0),n.apiKey&&t(s,n.apiKey,!0);const a=r.getFcmToken?.();a&&t(w,a,!0),z=!0}catch{}}t(A,tt(),!0)}De(()=>{{const r=window.location.hash.slice(1);if(r){const n=new URLSearchParams(r),a=n.get("key"),p=n.get("url");a&&(t(s,a,!0),p&&t(f,p,!0),Z(),window.history.replaceState({},"",window.location.pathname))}if(!e(s))try{const n=localStorage.getItem("shooter_config");if(n){const a=JSON.parse(n);ze(a)?(a.apiKey&&t(s,a.apiKey,!0),a.deviceToken&&t(w,a.deviceToken,!0),a.serverUrl&&t(f,a.serverUrl,!0)):localStorage.removeItem("shooter_config")}}catch{}if(ce(),e(f)||t(f,Y(),!0),t(M,!0),!e(A)){const n=setInterval(()=>{ce(),e(A)&&clearInterval(n)},200);setTimeout(()=>{clearInterval(n)},3e3)}}});async function Z(){t(S,!0),t(l,""),t(i,"");try{const r=e(f).trim().replace(/\/+$/,"");localStorage.setItem("shooter_config",JSON.stringify({apiKey:e(s).trim(),deviceToken:e(w).trim(),lastUpdated:Date.now(),serverUrl:r||Y()}));const n=X();n&&n.saveConfig?.(JSON.stringify({apiKey:e(s).trim(),serverUrl:r||Y()})),(await fetch("/api/health")).ok?(t(l,"Configuration saved successfully"),t(i,"success")):(t(l,"Configuration saved but system health check failed"),t(i,"warning"))}catch(r){t(l,`Configuration failed: ${H(r)}`),t(i,"error")}t(S,!1)}async function Ie(){if(!e(s).trim()){t(l,"API key is required for testing"),t(i,"error");return}t(S,!0),t(l,""),t(i,"");try{const r={data:{source:"config-test",timestamp:Date.now()},message:`Configuration test at ${new Date().toLocaleTimeString()}`,title:"Configuration Test"};e(w).trim()&&(r.deviceToken=e(w).trim());const n=await fetch("/api/notify",{body:JSON.stringify(r),headers:{Authorization:`Bearer ${e(s)}`,"Content-Type":"application/json"},method:"POST"}),a=await n.json();n.ok?(t(l,"Test notification sent successfully"),t(i,"success")):(t(l,`Test failed: ${typeof a.error=="string"?a.error:"Unknown error"}`),t(i,"error"))}catch(r){t(l,`Network error: ${H(r)}`),t(i,"error")}t(S,!1)}function Te(){{localStorage.removeItem("shooter_config"),t(f,typeof window<"u"?window.location.origin:"",!0),t(s,""),t(w,""),t(l,"Configuration cleared"),t(i,"success");const r=X();r&&r.saveConfig?.(JSON.stringify({apiKey:"",serverUrl:""}))}}var ee=St();Fe("1gp6n77",r=>{var n=at();Oe(()=>{Le.title="Settings - Shooter"}),d(r,n)});var le=v(ee),de=g(v(le),4),te=v(de),ve=v(te);G(ve,{title:"Server Configuration",description:"Configure server connection and credentials",children:(r,n)=>{var a=ot(),p=J(a);ie(p,{name:"serverUrl",label:"Server URL",dataType:"text",placeholder:"https://shooter.breezehq.dev",infoMessage:"Base URL of your Shooter server. Apps will reload with this URL on next launch.",get value(){return e(f)},set value(C){t(f,C,!0)}});var P=g(p,2);ie(P,{name:"apiKey",label:"API Key",dataType:"password",placeholder:"Enter your API key",infoMessage:"Required for sending notifications",get value(){return e(s)},set value(C){t(s,C,!0)}});var N=g(P,4);{let C=x(()=>e(I)?"Waiting for token...":"64-character hex string"),R=x(()=>e(I)&&e(w)?"Auto-detected from app":"Device token from app registration");ie(N,{name:"deviceToken",label:"Device Token",dataType:"text",get placeholder(){return e(C)},get infoMessage(){return e(R)},classes:"input-mono",get value(){return e(w)},set value(B){t(w,B,!0)}})}we(2),d(r,a)},$$slots:{default:!0}});var ue=g(ve,2);{var Ae=r=>{const n=x(()=>e(i)==="success"?Se:e(i)==="error"?ke:Ge);{const a=P=>{se(P,{get svg(){return e(n)},classes:"icon-16"})};let p=x(()=>e(i)||"info");Me(r,{get text(){return e(l)},get classes(){return`banner-${e(p)??""}`},icon:a,$$slots:{icon:!0}})}};K(ue,r=>{e(l)&&r(Ae)})}var ge=g(ue,2),fe=v(ge);{let r=x(()=>e(S)||!e(s).trim());F(fe,{classes:"btn-secondary",onclick:Ie,get disabled(){return e(r)},children:(n,a)=>{var p=it(),P=J(p);{var N=C=>{se(C,{get svg(){return nt},classes:"icon-14"})};K(P,C=>{e(S)||C(N)})}we(),d(n,p)},$$slots:{default:!0}})}var Be=g(fe,2);{let r=x(()=>e(S)||!e(s).trim());F(Be,{classes:"btn-primary",onclick:Z,get disabled(){return e(r)},get showLoader(){return e(S)},text:"Save Changes"})}c(ge),c(te);var pe=g(te,2),he=v(pe);G(he,{title:"Setup Guide",children:(r,n)=>{{let a=x(()=>e(s).trim()&&e(w).trim()?2:e(s).trim()?1:0);Xe(r,{steps:[{label:"Get API Key"},{label:"Find Device Token"},{label:"Test Connection"}],get currentStepIndex(){return e(a)},classes:"setup-stepper"})}},$$slots:{default:!0}});var me=g(he,2);{let r=x(()=>e(A)?"Scan a QR code to connect":"Scan to connect your mobile app");G(me,{title:"Mobile App Setup",get description(){return e(r)},children:(n,a)=>{var p=ft(),P=v(p);{var N=R=>{var B=st(),m=g(J(B),2);{let _=x(()=>e(V)?"Scanning...":"Scan QR Code");F(m,{classes:"btn-secondary btn-sm",onclick:Ue,get disabled(){return e(V)},get text(){return e(_)}})}d(R,B)},C=R=>{var B=gt(),m=J(B);{var _=y=>{var k=lt(),D=J(k),ne=v(D);c(D);var Qe=g(D,4);{var Ke=ae=>{var oe=ct(),_e=g(v(oe)),Ne=v(_e,!0);c(_e),c(oe),U(()=>O(Ne,e(L))),d(ae,oe)};K(Qe,ae=>{e(L)&&ae(Ke)})}U(()=>Ee(ne,"src",e(T))),d(y,k)},Q=y=>{var k=dt();d(y,k)},$=y=>{var k=vt(),D=v(k,!0);c(k),U(()=>O(D,e(q))),d(y,k)},E=y=>{var k=ut();d(y,k)};K(m,y=>{e(T)?y(_):e(j)?y(Q,1):e(q)?y($,2):y(E,-1)})}var re=g(m,2);{let y=x(()=>e(j)||!e(s).trim()),k=x(()=>e(T)?"Regenerate QR Code":"Generate QR Code");F(re,{classes:"btn-secondary btn-sm",onclick:Re,get disabled(){return e(y)},get text(){return e(k)}})}d(R,B)};K(P,R=>{e(A)?R(N):R(C,-1)})}c(p),d(n,p)},$$slots:{default:!0}})}var ye=g(me,2);G(ye,{title:"AI Providers",description:"NeuroLink-powered summaries and AI features",children:(r,n)=>{var a=_t(),p=v(a);xe(p,17,()=>Ve,m=>m.id,(m,_)=>{var Q=pt(),$=v(Q);{let ne=x(()=>o.data.aiProviders[e(_).id]?Se:ke);se($,{get svg(){return e(ne)},classes:"icon-14"})}var E=g($,2),re=v(E,!0);c(E);var y=g(E,2);let k;var D=v(y,!0);c(y),c(Q),U(()=>{O(re,e(_).label),k=W(y,1,"provider-status svelte-1gp6n77",null,k,{configured:o.data.aiProviders[e(_).id]}),O(D,o.data.aiProviders[e(_).id]?"configured":"not configured")}),d(m,Q)});var P=g(p,2);{var N=m=>{var _=ht(),Q=g(v(_)),$=v(Q,!0);c(Q),c(_),U(()=>O($,o.data.activeProvider)),d(m,_)},C=m=>{var _=mt();d(m,_)},R=x(()=>Object.values(o.data.aiProviders).some(Boolean)),B=m=>{var _=yt();d(m,_)};K(P,m=>{o.data.activeProvider?m(N):e(R)?m(C,1):m(B,-1)})}c(a),d(r,a)},$$slots:{default:!0}});var qe=g(ye,2);G(qe,{title:"Danger Zone",children:(r,n)=>{var a=wt(),p=g(J(a),2);F(p,{classes:"btn-danger btn-sm",onclick:Te,text:"Clear Configuration"}),d(r,a)},$$slots:{default:!0}}),c(pe),c(de),c(le),c(ee),d(h,ee),Ce()}export{Tt as component};