PageSourceSearch

https://stridenavigation.com/chunk-NM2GQ3VU.js

js stridenavigation.com collected 2026-09-24 14:53:18 UTC 18,824 bytes, 454 lines download raw bytes

1import {
2  MapInteractionService
3} from "./chunk-4Y5EMHGE.js";
4import {
5  DeviceIdentityService,
6  isRecordingTooLarge
7} from "./chunk-GEY5NERW.js";
8import {
9  MapRenderingDiagnosticsService,
10  NavigationService,
11  attachmentToBlob,
12  normalizeScreenshotMimeType,
13  screenshotExtension
14} from "./chunk-CZMIP4ZQ.js";
15import {
16  APP_VERSION
17} from "./chunk-ODH7QDZO.js";
18import {
19  BeaconLocationService,
20  LocationService
21} from "./chunk-K52XM66R.js";
22import {
23  BeaconService
24} from "./chunk-UZKEIBGO.js";
25import {
26  LocationSource
27} from "./chunk-2E4THSOE.js";
28import {
29  GPSService
30} from "./chunk-JISY7VJD.js";
31import {
32  SettingsService
33} from "./chunk-QI7JV4VS.js";
34import {
35  ApiUrlService
36} from "./chunk-MAIAEXGG.js";
37import {
38  HttpClient,
39  Injectable,
40  Router,
41  catchError,
42  combineLatest,
43  from,
44  map,
45  of,
46  setClassMetadata,
47  switchMap,
48  ɵɵdefineInjectable,
49  ɵɵinject
50} from "./chunk-UQ6LLOJ6.js";
51import {
52  __async
53} from "./chunk-CO64IJQE.js";
54
55// src/app/services/bug-report/recording-attachment.ts
56function capturedRecordingFromRecording(recording, filename) {
57  var _a, _b, _c;
58  const json = JSON.stringify(recording);
59  const byteSize = new TextEncoder().encode(json).length;
60  const eventCounts = { gps: 0, bluetooth: 0, marker: 0 };
61  for (const event of (_a = recording.events) != null ? _a : []) {
62    if (event.type === "gps") {
63      eventCounts.gps++;
64    } else if (event.type === "bluetooth") {
65      eventCounts.bluetooth++;
66    } else if (event.type === "marker") {
67      eventCounts.marker++;
68    }
69  }
70  return {
71    json,
72    filename,
73    byteSize,
74    durationMs: (_c = (_b = recording.metadata) == null ? void 0 : _b.duration) != null ? _c : 0,
75    eventCounts
76  };
77}
78
79// src/app/services/bug-report/bug-report.service.ts
80var _BugReportService = class _BugReportService {
81  constructor(http, apiUrlService, router, navigationService, locationService, beaconLocationService, mapInteractionService, beaconService, gpsService, settingsService, renderingDiagnostics, deviceIdentityService) {
82    this.http = http;
83    this.apiUrlService = apiUrlService;
84    this.router = router;
85    this.navigationService = navigationService;
86    this.locationService = locationService;
87    this.beaconLocationService = beaconLocationService;
88    this.mapInteractionService = mapInteractionService;
89    this.beaconService = beaconService;
90    this.gpsService = gpsService;
91    this.settingsService = settingsService;
92    this.renderingDiagnostics = renderingDiagnostics;
93    this.deviceIdentityService = deviceIdentityService;
94    this.apiBaseUrl = this.apiUrlService.getSelectedUrl() + "/api/v1";
95    this.apiUrlService.selectedUrl$.subscribe((url) => {
96      this.apiBaseUrl = url + "/api/v1";
97    });
98  }
99  /**
100   * Upload a screenshot for the bug report.
101   */
102  uploadScreenshot(screenshot) {
103    const sourceBlob = attachmentToBlob(screenshot);
104    const mimeType = normalizeScreenshotMimeType(sourceBlob.type || screenshot.mimeType);
105    const blob = sourceBlob.type === mimeType ? sourceBlob : new Blob([sourceBlob], { type: mimeType });
106    const filename = `screenshot.${screenshotExtension(mimeType)}`;
107    const formData = new FormData();
108    formData.append("file", blob, filename);
109    return this.http.post(`${this.apiBaseUrl}/BugReport/screenshot`, formData);
110  }
111  /**
112   * Upload a signal recording attachment for the bug report.
113   */
114  uploadRecording(recording) {
115    const blob = new Blob([recording.json], { type: "application/json" });
116    const filename = recording.filename.endsWith(".json") ? recording.filename : `${recording.filename}.json`;
117    const formData = new FormData();
118    formData.append("file", blob, filename);
119    return this.http.post(`${this.apiBaseUrl}/BugReport/recording`, formData);
120  }
121  /**
122   * Submit a bug report.
123   */
124  submitReport(request) {
125    return this.http.post(`${this.apiBaseUrl}/BugReport`, request);
126  }
127  /**
128   * Get device info including a persistent device identifier.
129   * Delegates to the shared DeviceIdentityService so all anonymous uploads
130   * (bug reports, standalone recordings) report the same identifier.
131   */
132  getDeviceInfo() {
133    return __async(this, null, function* () {
134      return this.deviceIdentityService.getDeviceInfo();
135    });
136  }
137  /**
138   * Collect comprehensive technical data from various services.
139   * Includes beacon signals, GPS status, confidence levels, and positioning state.
140   */
141  collectTechnicalData() {
142    var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w;
143    const navState = this.navigationService.currentState;
144    const beaconLocation = this.locationService.currentBeaconLocation;
145    const locationMode = this.locationService.mode;
146    const beaconModeStr = locationMode === "beacon" || locationMode === "dual" ? locationMode : "unknown";
147    const mapState = (_c = (_b = (_a = this.mapInteractionService).getCurrentState) == null ? void 0 : _b.call(_a)) != null ? _c : {
148      isManualOverride: false
149    };
150    const beaconSnapshot = this.beaconService.getBeaconDebugSnapshot();
151    const timeSinceLastBeacon = this.beaconLocationService.getTimeSinceLastBeaconMs();
152    const signalQuality = this.calculateSignalQuality(beaconSnapshot);
153    const kalmanState = this.beaconLocationService.getKalmanDebugState();
154    const stablePair = this.beaconLocationService.getStablePairDebugState();
155    const gpsDebug = this.getGPSDebugState();
156    const confidenceState = this.getConfidenceState();
157    const sourceVoting = this.getSourceVotingState();
158    const settings = this.getSettingsSnapshot();
159    return {
160      navigation: {
161        mode: (_d = navState == null ? void 0 : navState.mode) != null ? _d : "idle",
162        targetType: (_e = navState == null ? void 0 : navState.target) == null ? void 0 : _e.type,
163        targetId: (_f = navState == null ? void 0 : navState.target) == null ? void 0 : _f.id,
164        targetName: (_g = navState == null ? void 0 : navState.target) == null ? void 0 : _g.name,
165        distanceRemaining: navState == null ? void 0 : navState.distanceToDestination,
166        currentDirection: (_h = navState == null ? void 0 : navState.currentDirection) == null ? void 0 : _h.instruction,
167        isOffRoute: (_i = navState == null ? void 0 : navState.isOffRoute) != null ? _i : false
168      },
169      position: {
170        x: (_j = beaconLocation == null ? void 0 : beaconLocation.point) == null ? void 0 : _j.x,
171        y: (_k = beaconLocation == null ? void 0 : beaconLocation.point) == null ? void 0 : _k.y,
172        floorId: (_m = (_l = beaconLocation == null ? void 0 : beaconLocation.floor) == null ? void 0 : _l.entity) == null ? void 0 : _m.id,
173        floorName: (_p = (_o = (_n = beaconLocation == null ? void 0 : beaconLocation.floor) == null ? void 0 : _n.entity) == null ? void 0 : _o.name) != null ? _p : void 0,
174        buildingId: (_r = (_q = beaconLocation == null ? void 0 : beaconLocation.building) == null ? void 0 : _q.entity) == null ? void 0 : _r.id,
175        buildingName: (_u = (_t = (_s = beaconLocation == null ? void 0 : beaconLocation.building) == null ? void 0 : _s.entity) == null ? void 0 : _t.name) != null ? _u : void 0,
176        confidence: beaconLocation == null ? void 0 : beaconLocation.confidence,
177        source: beaconLocation ? "beacon" : "none",
178        activeSource: confidenceState.activeSource,
179        kalmanState,
180        stablePair
181      },
182      confidence: confidenceState,
183      beacons: {
184        count: beaconSnapshot.length,
185        nearestSignalStrength: (_v = beaconSnapshot[0]) == null ? void 0 : _v.rssi,
186        mode: beaconModeStr,
187        timeSinceLastMs: timeSinceLastBeacon,
188        signalQuality,
189        activeBeacons: beaconSnapshot
190      },
191      gps: gpsDebug,
192      sourceVoting,
193      map: {
194        isManualOverride: (_w = mapState.isManualOverride) != null ? _w : false,
195        currentScale: void 0,
196        rotation: void 0
197      },
198      settings,
199      route: this.router.url,
200      appVersion: APP_VERSION,
201      timestamp: (/* @__PURE__ */ new Date()).toISOString(),
202      rendering: this.renderingDiagnostics.getCompactSnapshot()
203    };
204  }
205  // =============================================================================
206  // Helper methods for debug data collection
207  // =============================================================================
208  /**
209   * Get GPS debug state including status and position.
210   */
211  getGPSDebugState() {
212    const gpsStatus = this.gpsService.currentGpsStatus;
213    const gpsPosition = this.gpsService.currentPosition;
214    return {
215      status: {
216        available: gpsStatus.available,
217        recovering: gpsStatus.recovering,
218        retryAttempt: gpsStatus.retryAttempt,
219        lastError: gpsStatus.lastError
220      },
221      position: gpsPosition ? {
222        latitude: gpsPosition.latitude,
223        longitude: gpsPosition.longitude,
224        accuracyMeters: gpsPosition.accuracy,
225        altitude: gpsPosition.altitude,
226        heading: gpsPosition.heading,
227        speed: gpsPosition.speed
228      } : null,
229      timeSinceLastMs: (gpsPosition == null ? void 0 : gpsPosition.recordedAt) ? Date.now() - gpsPosition.recordedAt.getTime() : null
230    };
231  }
232  /**
233   * Get dual confidence state comparing beacon and GPS.
234   */
235  getConfidenceState() {
236    var _a, _b, _c, _d, _e;
237    const beaconLoc = this.locationService.currentBeaconLocation;
238    const sourceVoting = this.locationService.sourceVotingState;
239    const beaconRaw = (_a = beaconLoc == null ? void 0 : beaconLoc.confidence) != null ? _a : 0;
240    const beaconAdj = (_b = beaconLoc == null ? void 0 : beaconLoc.confidenceAdjusted) != null ? _b : beaconRaw;
241    const gpsLoc = (_c = this.locationService._gpsLocation$) == null ? void 0 : _c.value;
242    const gpsRaw = (_d = gpsLoc == null ? void 0 : gpsLoc.confidence) != null ? _d : 0;
243    const gpsAdj = (_e = gpsLoc == null ? void 0 : gpsLoc.confidenceAdjusted) != null ? _e : gpsRaw;
244    let activeSource = "none";
245    if (sourceVoting.currentSource === LocationSource.BEACON) {
246      activeSource = "beacon";
247    } else if (sourceVoting.currentSource === LocationSource.GPS) {
248      activeSource = "gps";
249    }
250    return {
251      beacon: beaconLoc ? {
252        raw: beaconRaw,
253        adjusted: beaconAdj
254      } : null,
255      gps: gpsLoc ? {
256        raw: gpsRaw,
257        adjusted: gpsAdj
258      } : null,
259      delta: Math.abs(beaconAdj - gpsAdj),
260      activeSource
261    };
262  }
263  /**
264   * Get source voting state for beacon/GPS switching.
265   */
266  getSourceVotingState() {
267    const voting = this.locationService.sourceVotingState;
268    let targetSource = null;
269    if (voting.targetSource === LocationSource.BEACON) {
270      targetSource = "beacon";
271    } else if (voting.targetSource === LocationSource.GPS) {
272      targetSource = "gps";
273    }
274    let currentSource = "beacon";
275    if (voting.currentSource === LocationSource.GPS) {
276      currentSource = "gps";
277    }
278    return {
279      isVoting: voting.isVoting,
280      progress: voting.progress,
281      targetSource,
282      currentSource,
283      timeElapsedMs: voting.timeElapsed,
284      timeRequiredMs: voting.timeRequired
285    };
286  }
287  /**
288   * Calculate signal quality metrics from beacon snapshots.
289   */
290  calculateSignalQuality(beacons) {
291    if (beacons.length === 0)
292      return null;
293    const rssiValues = beacons.map((b) => b.rssi);
294    return {
295      avgRssi: rssiValues.reduce((a, b) => a + b, 0) / rssiValues.length,
296      strongestRssi: Math.max(...rssiValues),
297      weakestRssi: Math.min(...rssiValues)
298    };
299  }
300  /**
301   * Get settings snapshot at bug report time.
302   */
303  getSettingsSnapshot() {
304    var _a, _b, _c;
305    const settings = this.settingsService.currentSettings;
306    return {
307      positioningMode: settings.positioningMode || "STABLE",
308      rssiFilterType: settings.rssiFilterType || "simpleEma",
309      kalmanEnabled: (_a = settings.enableGraphKalmanFilter) != null ? _a : true,
310      beaconDebugEnabled: (_b = settings.enableBeaconDebugLogging) != null ? _b : false,
311      smartSwitchingEnabled: (_c = settings.enableSmartSourceSwitching) != null ? _c : true,
312      locationMode: this.locationService.mode
313    };
314  }
315  /**
316   * Get current campus ID if available.
317   */
318  getCurrentCampusId() {
319    var _a, _b, _c;
320    return (_c = (_b = (_a = this.locationService.currentBeaconLocation) == null ? void 0 : _a.campus) == null ? void 0 : _b.entity) == null ? void 0 : _c.id;
321  }
322  /**
323   * Build a complete bug report request.
324   */
325  buildReportRequest(description, category, screenshotUploadId, technicalData, recordingUploadId) {
326    return __async(this, null, function* () {
327      const deviceInfo = yield this.getDeviceInfo();
328      const techData = technicalData != null ? technicalData : this.collectTechnicalData();
329      return {
330        description,
331        category,
332        deviceIdentifier: deviceInfo.deviceIdentifier,
333        deviceModel: deviceInfo.model,
334        osVersion: deviceInfo.osVersion,
335        appVersion: deviceInfo.appVersion,
336        screenshotUploadId,
337        recordingUploadId,
338        technicalDataJson: JSON.stringify(techData),
339        campusId: this.getCurrentCampusId()
340      };
341    });
342  }
343  /**
344   * Submit a complete bug report (screenshot upload + recording upload + report creation).
345   *
346   * Attachment uploads are best-effort: if the screenshot or recording upload
347   * fails the report is still submitted without that attachment. Recording
348   * attachments that exceed the upload cap are never attempted.
349   */
350  submitCompleteReport(description, category, screenshot, technicalData, recording) {
351    const reportTechnicalData = technicalData != null ? technicalData : this.collectTechnicalData();
352    const screenshotUpload$ = screenshot ? this.uploadScreenshot(screenshot).pipe(map((upload) => upload == null ? void 0 : upload.id), catchError((error) => {
353      console.warn("Screenshot upload failed, submitting without screenshot:", error);
354      return of(void 0);
355    })) : of(void 0);
356    const canUploadRecording = !!recording && !isRecordingTooLarge(recording);
357    if (recording && !canUploadRecording) {
358      console.warn(`[BugReportService] Recording ${recording.filename} exceeds the upload cap; submitting without it`);
359    }
360    const recordingUpload$ = canUploadRecording ? this.uploadRecording(recording).pipe(map((upload) => upload == null ? void 0 : upload.id), catchError((error) => {
361      console.warn("Signal recording upload failed, submitting without recording:", error);
362      return of(void 0);
363    })) : of(void 0);
364    return combineLatest([screenshotUpload$, recordingUpload$]).pipe(switchMap(([screenshotUploadId, recordingUploadId]) => from(this.buildReportRequest(description, category, screenshotUploadId, reportTechnicalData, recordingUploadId))), switchMap((request) => this.submitReport(request)));
365  }
366  // ============================================
367  // Admin API methods
368  // ============================================
369  /**
370   * Get paginated list of bug reports (Admin only).
371   */
372  getReportList(params = {}) {
373    const queryParams = new URLSearchParams();
374    if (params.status)
375      queryParams.set("status", params.status);
376    if (params.category)
377      queryParams.set("category", params.category);
378    if (params.campusId)
379      queryParams.set("campusId", params.campusId);
380    if (params.deviceIdentifier)
381      queryParams.set("deviceIdentifier", params.deviceIdentifier);
382    if (params.page)
383      queryParams.set("page", params.page.toString());
384    if (params.pageSize)
385      queryParams.set("pageSize", params.pageSize.toString());
386    const queryString = queryParams.toString();
387    const url = `${this.apiBaseUrl}/BugReport${queryString ? "?" + queryString : ""}`;
388    return this.http.get(url);
389  }
390  /**
391   * Get a single bug report by ID (Admin only).
392   */
393  getReport(id) {
394    return this.http.get(`${this.apiBaseUrl}/BugReport/${id}`);
395  }
396  /**
397   * Update bug report status (Admin only).
398   */
399  updateReportStatus(id, status, adminNotes) {
400    return this.http.patch(`${this.apiBaseUrl}/BugReport/${id}/status`, {
401      status,
402      adminNotes
403    });
404  }
405  /**
406   * Delete a bug report (Admin only).
407   */
408  deleteReport(id) {
409    return this.http.delete(`${this.apiBaseUrl}/BugReport/${id}`);
410  }
411  /**
412   * Get bug report statistics (Admin only).
413   */
414  getStats() {
415    return this.http.get(`${this.apiBaseUrl}/BugReport/stats`);
416  }
417};
418_BugReportService.\u0275fac = function BugReportService_Factory(__ngFactoryType__) {
419  return new (__ngFactoryType__ || _BugReportService)(\u0275\u0275inject(HttpClient), \u0275\u0275inject(ApiUrlService), \u0275\u0275inject(Router), \u0275\u0275inject(NavigationService), \u0275\u0275inject(LocationService), \u0275\u0275inject(BeaconLocationService), \u0275\u0275inject(MapInteractionService), \u0275\u0275inject(BeaconService), \u0275\u0275inject(GPSService), \u0275\u0275inject(SettingsService), \u0275\u0275inject(MapRenderingDiagnosticsService), \u0275\u0275inject(DeviceIdentityService));
420};
421_BugReportService.\u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _BugReportService, factory: _BugReportService.\u0275fac, providedIn: "root" });
422var BugReportService = _BugReportService;
423(() => {
424  (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(BugReportService, [{
425    type: Injectable,
426    args: [{
427      providedIn: "root"
428    }]
429  }], () => [{ type: HttpClient }, { type: ApiUrlService }, { type: Router }, { type: NavigationService }, { type: LocationService }, { type: BeaconLocationService }, { type: MapInteractionService }, { type: BeaconService }, { type: GPSService }, { type: SettingsService }, { type: MapRenderingDiagnosticsService }, { type: DeviceIdentityService }], null);
430})();
431
432// src/app/services/bug-report/bug-report.model.ts
433var BUG_REPORT_CATEGORIES = [
434  { value: "navigation", labelKey: "bugReport.category.navigation", icon: "lucideNavigation" },
435  { value: "positioning", labelKey: "bugReport.category.positioning", icon: "lucideMapPin" },
436  { value: "map_display", labelKey: "bugReport.category.mapDisplay", icon: "lucideMap" },
437  { value: "performance", labelKey: "bugReport.category.performance", icon: "lucideGauge" },
438  { value: "ui", labelKey: "bugReport.category.ui", icon: "lucideLayoutDashboard" },
439  { value: "other", labelKey: "bugReport.category.other", icon: "lucideCircleHelp" }
440];
441var BUG_REPORT_STATUSES = [
442  { value: "new", labelKey: "bugReport.status.new" },
443  { value: "acknowledged", labelKey: "bugReport.status.acknowledged" },
444  { value: "in_progress", labelKey: "bugReport.status.inProgress" },
445  { value: "resolved", labelKey: "bugReport.status.resolved" },
446  { value: "closed", labelKey: "bugReport.status.closed" }
447];
448
449export {
450  capturedRecordingFromRecording,
451  BugReportService,
452  BUG_REPORT_CATEGORIES,
453  BUG_REPORT_STATUSES
454};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.