becomap 1.6.2 → 1.6.8

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.
package/public/index.html CHANGED
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
3
 
4
4
  <head>
@@ -22,43 +22,177 @@
22
22
 
23
23
  <body>
24
24
  <div id="mapContainer"></div>
25
-
26
- <!-- External libs -->
27
25
  <script src="https://unpkg.com/maplibre-gl@4.4.1/dist/maplibre-gl.js"></script>
28
26
  <script src="https://unpkg.com/@turf/turf@7.1.0/turf.min.js"></script>
29
-
30
- <!-- Core Map SDK Integration -->
31
- <script>
32
- // ============================================================================
27
+ <script>// ============================================================================
33
28
  // GLOBAL VARIABLES
34
29
  // ============================================================================
35
30
  window._mapView = null;
36
31
  window._site = null;
37
32
  window._eventListeners = new Map();
33
+ window._bridgeHealth = {
34
+ isConnected: false,
35
+ lastHeartbeat: null,
36
+ connectionAttempts: 0,
37
+ maxRetries: 3
38
+ };
39
+ window._operationQueue = [];
40
+ window._isProcessingQueue = false;
41
+ window._appState = 'initializing'; // 'initializing', 'ready', 'error', 'destroyed'
38
42
 
39
43
  // ============================================================================
40
44
  // UTILITY FUNCTIONS
41
45
  // ============================================================================
42
46
 
43
47
  /**
44
- * Sends messages to native platform (iOS/Android)
48
+ * Checks if native bridge is available
49
+ * @returns {boolean} - True if native bridge is available
50
+ */
51
+ function isNativeBridgeAvailable() {
52
+ return !!(window.webkit?.messageHandlers?.jsHandler || window.jsHandler?.postMessage);
53
+ }
54
+
55
+ /**
56
+ * Updates bridge health status
57
+ * @param {boolean} isConnected - Connection status
58
+ */
59
+ function updateBridgeHealth(isConnected) {
60
+ window._bridgeHealth.isConnected = isConnected;
61
+ window._bridgeHealth.lastHeartbeat = Date.now();
62
+
63
+ if (isConnected) {
64
+ window._bridgeHealth.connectionAttempts = 0;
65
+ processOperationQueue();
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Sends messages to native platform with retry mechanism
45
71
  * @param {string} type - Message type
46
72
  * @param {any} payload - Message payload
73
+ * @param {boolean} skipQueue - Skip queue and send immediately
74
+ */
75
+ function notifyNative(type, payload = null, skipQueue = false) {
76
+ const message = { type, payload, timestamp: Date.now() };
77
+
78
+ if (!isNativeBridgeAvailable()) {
79
+ if (!skipQueue && window._bridgeHealth.connectionAttempts < window._bridgeHealth.maxRetries) {
80
+ queueOperation(() => notifyNative(type, payload, true));
81
+ return;
82
+ }
83
+
84
+ // Fallback: store in localStorage for debugging
85
+ try {
86
+ const failedMessages = JSON.parse(localStorage.getItem('becomap_failed_messages') || '[]');
87
+ failedMessages.push(message);
88
+ localStorage.setItem('becomap_failed_messages', JSON.stringify(failedMessages.slice(-50))); // Keep last 50
89
+ } catch (e) {
90
+ // Silent fail for localStorage errors in production
91
+ }
92
+ return;
93
+ }
94
+
95
+ try {
96
+ const messageStr = JSON.stringify(message);
97
+
98
+ if (window.webkit?.messageHandlers?.jsHandler) {
99
+ // iOS
100
+ window.webkit.messageHandlers.jsHandler.postMessage(messageStr);
101
+ } else if (window.jsHandler?.postMessage) {
102
+ // Android
103
+ window.jsHandler.postMessage(messageStr);
104
+ }
105
+
106
+ updateBridgeHealth(true);
107
+ } catch (error) {
108
+ updateBridgeHealth(false);
109
+
110
+ if (!skipQueue) {
111
+ queueOperation(() => notifyNative(type, payload, true));
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Queues an operation for later execution
118
+ * @param {Function} operation - Operation to queue
119
+ */
120
+ function queueOperation(operation) {
121
+ window._operationQueue.push({
122
+ operation,
123
+ timestamp: Date.now(),
124
+ retries: 0
125
+ });
126
+
127
+ // Process queue after a short delay
128
+ setTimeout(processOperationQueue, 100);
129
+ }
130
+
131
+ /**
132
+ * Processes queued operations
47
133
  */
48
- function notifyNative(type, payload = null) {
49
- const message = JSON.stringify({ type, payload });
50
-
51
- if (window.webkit?.messageHandlers?.jsHandler) {
52
- // iOS
53
- window.webkit.messageHandlers.jsHandler.postMessage(message);
54
- } else if (window.jsHandler?.postMessage) {
55
- // Android
56
- window.jsHandler.postMessage(message);
57
- } else {
58
- console.log("Native handler not available:", message);
134
+ function processOperationQueue() {
135
+ if (window._isProcessingQueue || !isNativeBridgeAvailable()) {
136
+ return;
137
+ }
138
+
139
+ window._isProcessingQueue = true;
140
+
141
+ try {
142
+ const now = Date.now();
143
+ const maxAge = 30000; // 30 seconds
144
+
145
+ // Remove expired operations
146
+ window._operationQueue = window._operationQueue.filter(item =>
147
+ now - item.timestamp < maxAge
148
+ );
149
+
150
+ // Process remaining operations
151
+ while (window._operationQueue.length > 0 && isNativeBridgeAvailable()) {
152
+ const item = window._operationQueue.shift();
153
+
154
+ try {
155
+ item.operation();
156
+ } catch (error) {
157
+ // Retry failed operations up to 3 times
158
+ if (item.retries < 3) {
159
+ item.retries++;
160
+ window._operationQueue.unshift(item);
161
+ break;
162
+ }
163
+ }
164
+ }
165
+ } finally {
166
+ window._isProcessingQueue = false;
59
167
  }
60
168
  }
61
169
 
170
+ /**
171
+ * Throttle function to prevent event flooding
172
+ * @param {Function} func - Function to throttle
173
+ * @param {number} delay - Delay in milliseconds
174
+ * @returns {Function} - Throttled function
175
+ */
176
+ function throttle(func, delay) {
177
+ let timeoutId;
178
+ let lastExecTime = 0;
179
+
180
+ return function (...args) {
181
+ const currentTime = Date.now();
182
+
183
+ if (currentTime - lastExecTime > delay) {
184
+ func.apply(this, args);
185
+ lastExecTime = currentTime;
186
+ } else {
187
+ clearTimeout(timeoutId);
188
+ timeoutId = setTimeout(() => {
189
+ func.apply(this, args);
190
+ lastExecTime = Date.now();
191
+ }, delay - (currentTime - lastExecTime));
192
+ }
193
+ };
194
+ }
195
+
62
196
  /**
63
197
  * Safely converts objects to JSON with error handling
64
198
  * @param {any} obj - Object to convert
@@ -68,28 +202,326 @@
68
202
  try {
69
203
  return obj?.toJSON ? obj.toJSON() : obj;
70
204
  } catch (error) {
71
- console.warn('Error converting to JSON:', error);
72
205
  return obj;
73
206
  }
74
207
  }
75
208
 
76
209
  /**
77
- * Executes function with error handling and native notification
210
+ * Executes function with enhanced error handling, timeout, and retry mechanism
78
211
  * @param {Function} fn - Function to execute
79
212
  * @param {string} errorType - Error type for native notification
213
+ * @param {any} errorDetails - Additional error details to include in the payload
80
214
  * @param {any} fallbackValue - Fallback value if function fails
215
+ * @param {number} timeout - Timeout in milliseconds (default: 5000)
216
+ * @param {number} maxRetries - Maximum retry attempts (default: 0)
217
+ */
218
+ function executeWithErrorHandling(fn, errorType, errorDetails = null, fallbackValue = null, timeout = 5000, maxRetries = 0) {
219
+ return new Promise((resolve) => {
220
+ let retryCount = 0;
221
+
222
+ function attemptExecution() {
223
+ try {
224
+ // Validate pre-conditions
225
+ if (window._appState === 'destroyed') {
226
+ throw new Error('Application has been destroyed');
227
+ }
228
+
229
+ if (!window._mapView && errorDetails?.operation !== 'init') {
230
+ throw new Error('MapView not initialized');
231
+ }
232
+
233
+ // Set up timeout
234
+ const timeoutId = setTimeout(() => {
235
+ throw new Error(`Operation timed out after ${timeout}ms`);
236
+ }, timeout);
237
+
238
+ const result = fn();
239
+ clearTimeout(timeoutId);
240
+
241
+ // Handle promises
242
+ if (result && typeof result.then === 'function') {
243
+ result
244
+ .then(res => resolve(res !== undefined ? res : fallbackValue))
245
+ .catch(handleError);
246
+ } else {
247
+ resolve(result !== undefined ? result : fallbackValue);
248
+ }
249
+
250
+ } catch (error) {
251
+ handleError(error);
252
+ }
253
+ }
254
+
255
+ function handleError(error) {
256
+ // Retry logic for transient errors
257
+ if (retryCount < maxRetries && isRetriableError(error)) {
258
+ retryCount++;
259
+ const delay = Math.min(1000 * Math.pow(2, retryCount - 1), 5000); // Exponential backoff
260
+ setTimeout(attemptExecution, delay);
261
+ return;
262
+ }
263
+
264
+ // Simple error payload for mobile
265
+ const errorPayload = {
266
+ operation: errorDetails?.operation || 'unknown',
267
+ error: getSimpleErrorMessage(error)
268
+ };
269
+
270
+ notifyNative(errorType, errorPayload);
271
+ resolve(fallbackValue);
272
+ }
273
+
274
+ attemptExecution();
275
+ });
276
+ }
277
+
278
+ /**
279
+ * Determines if an error is retriable
280
+ * @param {Error} error - The error to check
281
+ * @returns {boolean} - True if error is retriable
282
+ */
283
+ function isRetriableError(error) {
284
+ const retriableMessages = [
285
+ 'network error',
286
+ 'timeout',
287
+ 'connection failed',
288
+ 'temporary failure'
289
+ ];
290
+
291
+ return retriableMessages.some(msg =>
292
+ error.message.toLowerCase().includes(msg)
293
+ );
294
+ }
295
+
296
+ /**
297
+ * Validates route parameters for mobile SDK consumption
298
+ * @param {any} startID - Starting location ID
299
+ * @param {any} goalID - Goal location ID
300
+ * @param {any} waypoints - Array of waypoint IDs
301
+ * @param {any} routeOptions - Route calculation options
302
+ * @returns {string|null} - Error message or null if valid
81
303
  */
82
- function executeWithErrorHandling(fn, errorType, fallbackValue = null) {
304
+ function validateRouteParameters(startID, goalID, waypoints, routeOptions) {
305
+ if (!startID || (typeof startID !== 'string' && typeof startID !== 'number')) {
306
+ return "Invalid start location ID";
307
+ }
308
+
309
+ if (!goalID || (typeof goalID !== 'string' && typeof goalID !== 'number')) {
310
+ return "Invalid goal location ID";
311
+ }
312
+
313
+ if (startID === goalID) {
314
+ return "Start and goal locations cannot be the same";
315
+ }
316
+
317
+ if (waypoints && !Array.isArray(waypoints)) {
318
+ return "Waypoints must be an array";
319
+ }
320
+
321
+ if (waypoints && waypoints.length > 10) {
322
+ return "Too many waypoints (maximum 10 allowed)";
323
+ }
324
+
325
+ return null;
326
+ }
327
+
328
+ /**
329
+ * Validates route data integrity
330
+ * @param {any} route - Route object to validate
331
+ * @returns {boolean} - True if route is valid
332
+ */
333
+ function isValidRoute(route) {
334
+ return route &&
335
+ route.segments &&
336
+ Array.isArray(route.segments) &&
337
+ route.segments.length > 0;
338
+ }
339
+
340
+ /**
341
+ * Validates segment index for route display
342
+ * @param {any} segmentIndex - Segment order index
343
+ * @param {any} routeController - Route controller instance
344
+ * @returns {string|null} - Error message or null if valid
345
+ */
346
+ function validateSegmentIndex(segmentIndex, routeController) {
347
+ if (typeof segmentIndex !== 'number' || segmentIndex < 0) {
348
+ return "Segment index must be a non-negative number";
349
+ }
350
+
351
+ const segments = routeController.segments;
352
+ if (!segments || segments.length === 0) {
353
+ return "No route segments available";
354
+ }
355
+
356
+ if (segmentIndex >= segments.length) {
357
+ return `Segment index ${segmentIndex} exceeds available segments (${segments.length})`;
358
+ }
359
+
360
+ return null;
361
+ }
362
+
363
+ /**
364
+ * Converts error objects to simple messages for mobile SDK
365
+ * @param {Error} error - Error object
366
+ * @returns {string} - Simplified error message
367
+ */
368
+ function getSimpleErrorMessage(error) {
369
+ if (!error) return "Unknown error occurred";
370
+
371
+ // Map common technical errors to user-friendly messages
372
+ const errorMappings = {
373
+ 'TypeError': 'Invalid data provided',
374
+ 'ReferenceError': 'Required component not available',
375
+ 'NetworkError': 'Network connection failed',
376
+ 'TimeoutError': 'Operation timed out'
377
+ };
378
+
379
+ const errorType = error.constructor.name;
380
+ return errorMappings[errorType] || error.message || "Operation failed";
381
+ }
382
+
383
+
384
+
385
+ /**
386
+ * Creates standardized parameter validation for mobile functions
387
+ * @param {Object} params - Parameters to validate
388
+ * @param {Object} schema - Validation schema
389
+ * @returns {string|null} - Error message or null if valid
390
+ */
391
+ function validateMobileParameters(params, schema) {
392
+ for (const [key, rules] of Object.entries(schema)) {
393
+ const value = params[key];
394
+
395
+ if (rules.required && (value === null || value === undefined)) {
396
+ return `Missing required parameter: ${key}`;
397
+ }
398
+
399
+ if (value !== undefined) {
400
+ if (rules.type && typeof value !== rules.type) {
401
+ return `Invalid ${key}: expected ${rules.type}`;
402
+ }
403
+
404
+ if (rules.range && (value < rules.range[0] || value > rules.range[1])) {
405
+ return `${key} out of valid range`;
406
+ }
407
+
408
+ if (rules.maxLength && value.length > rules.maxLength) {
409
+ return `${key} exceeds maximum length`;
410
+ }
411
+ }
412
+ }
413
+
414
+ return null;
415
+ }
416
+
417
+ /**
418
+ * Advanced error handling with circuit breaker pattern
419
+ */
420
+ const errorCircuitBreaker = {
421
+ failures: new Map(),
422
+ thresholds: {
423
+ maxFailures: 5,
424
+ resetTimeout: 30000 // 30 seconds
425
+ },
426
+
427
+ isCircuitOpen(operation) {
428
+ const failure = this.failures.get(operation);
429
+ if (!failure) return false;
430
+
431
+ if (failure.count >= this.thresholds.maxFailures) {
432
+ if (Date.now() - failure.lastFailure < this.thresholds.resetTimeout) {
433
+ return true;
434
+ } else {
435
+ // Reset circuit
436
+ this.failures.delete(operation);
437
+ return false;
438
+ }
439
+ }
440
+ return false;
441
+ },
442
+
443
+ recordFailure(operation) {
444
+ const failure = this.failures.get(operation) || { count: 0, lastFailure: 0 };
445
+ failure.count++;
446
+ failure.lastFailure = Date.now();
447
+ this.failures.set(operation, failure);
448
+ },
449
+
450
+ recordSuccess(operation) {
451
+ this.failures.delete(operation);
452
+ }
453
+ };
454
+
455
+ /**
456
+ * Enhanced error handling with circuit breaker and caching
457
+ */
458
+ function executeWithCircuitBreaker(operation, fn, errorCallback) {
459
+ if (errorCircuitBreaker.isCircuitOpen(operation)) {
460
+ errorCallback({
461
+ operation,
462
+ error: "Service temporarily unavailable"
463
+ });
464
+ return;
465
+ }
466
+
83
467
  try {
84
468
  const result = fn();
85
- return result !== undefined ? result : fallbackValue;
469
+ errorCircuitBreaker.recordSuccess(operation);
470
+ return result;
86
471
  } catch (error) {
87
- console.error(`${errorType} error:`, error);
88
- notifyNative(errorType, { message: error.message });
89
- return fallbackValue;
472
+ errorCircuitBreaker.recordFailure(operation);
473
+ errorCallback({
474
+ operation,
475
+ error: getSimpleErrorMessage(error)
476
+ });
90
477
  }
91
478
  }
92
479
 
480
+ /**
481
+ * Debounced error reporting to prevent spam
482
+ */
483
+ const errorDebouncer = {
484
+ timers: new Map(),
485
+ delay: 1000, // 1 second
486
+
487
+ debounce(key, callback) {
488
+ if (this.timers.has(key)) {
489
+ clearTimeout(this.timers.get(key));
490
+ }
491
+
492
+ const timer = setTimeout(() => {
493
+ callback();
494
+ this.timers.delete(key);
495
+ }, this.delay);
496
+
497
+ this.timers.set(key, timer);
498
+ }
499
+ };
500
+
501
+ /**
502
+ * Memory-efficient error caching
503
+ */
504
+ const errorCache = {
505
+ cache: new Map(),
506
+ maxSize: 50,
507
+
508
+ set(key, value) {
509
+ if (this.cache.size >= this.maxSize) {
510
+ const firstKey = this.cache.keys().next().value;
511
+ this.cache.delete(firstKey);
512
+ }
513
+ this.cache.set(key, value);
514
+ },
515
+
516
+ get(key) {
517
+ return this.cache.get(key);
518
+ },
519
+
520
+ has(key) {
521
+ return this.cache.has(key);
522
+ }
523
+ };
524
+
93
525
  // ============================================================================
94
526
  // BCMapViewEvents CALLBACKS
95
527
  // ============================================================================
@@ -109,44 +541,87 @@
109
541
 
110
542
  // Map load event
111
543
  const loadListenerId = mapView.eventsHandler.on('load', () => {
112
- notifyNative("mapLoad", { timestamp: Date.now() });
544
+ try {
545
+ window._appState = 'ready';
546
+ notifyNative("onRenderComplete", {
547
+ site: safeToJSON(window._site)
548
+ });
549
+ } catch (error) {
550
+ notifyNative("onError", {
551
+ operation: 'load',
552
+ error: getSimpleErrorMessage(error)
553
+ });
554
+ }
113
555
  });
114
556
 
115
- // View change event
116
- const viewChangeListenerId = mapView.eventsHandler.on('viewChange', (args) => {
117
- notifyNative("viewChange", {
118
- viewOptions: safeToJSON(args.viewOptions),
119
- timestamp: Date.now()
120
- });
121
- });
557
+ // View change event (throttled to prevent flooding)
558
+ const throttledViewChange = throttle((args) => {
559
+ try {
560
+ notifyNative("onViewChange", {
561
+ viewOptions: safeToJSON(args.viewOptions)
562
+ });
563
+ } catch (error) {
564
+ // Silent fail for view change errors to prevent spam
565
+ }
566
+ }, 100); // Throttle to max 10 events per second
567
+
568
+ const viewChangeListenerId = mapView.eventsHandler.on('viewChange', throttledViewChange);
122
569
 
123
570
  // Location selection event
124
571
  const selectListenerId = mapView.eventsHandler.on('select', (args) => {
125
- notifyNative("locationSelect", {
126
- locations: args.locations?.map(loc => safeToJSON(loc)) || [],
127
- timestamp: Date.now()
128
- });
572
+ try {
573
+ notifyNative("onLocationSelect", {
574
+ locations: args.locations?.map(loc => safeToJSON(loc)) || []
575
+ });
576
+ } catch (error) {
577
+ notifyNative("onError", {
578
+ operation: 'select',
579
+ error: getSimpleErrorMessage(error)
580
+ });
581
+ }
129
582
  });
130
583
 
131
584
  // Floor switch event
132
585
  const switchToFloorListenerId = mapView.eventsHandler.on('switchToFloor', (args) => {
133
- notifyNative("floorSwitch", {
134
- floor: safeToJSON(args.floor),
135
- timestamp: Date.now()
136
- });
586
+ try {
587
+ notifyNative("onFloorSwitch", {
588
+ floor: safeToJSON(args.floor)
589
+ });
590
+ } catch (error) {
591
+ console.error('Error in switchToFloor event handler:', error);
592
+ notifyNative("onError", {
593
+ operation: "switchToFloor",
594
+ error: getSimpleErrorMessage(error)
595
+ });
596
+ }
137
597
  });
138
598
 
139
599
  // Route step load event
140
600
  const stepLoadListenerId = mapView.eventsHandler.on('stepLoad', (args) => {
141
- notifyNative("stepLoad", {
142
- step: safeToJSON(args.step),
143
- timestamp: Date.now()
144
- });
601
+ try {
602
+ notifyNative("onStepLoad", {
603
+ step: safeToJSON(args.step)
604
+ });
605
+ } catch (error) {
606
+ console.error('Error in stepLoad event handler:', error);
607
+ notifyNative("onError", {
608
+ operation: "stepLoad",
609
+ error: getSimpleErrorMessage(error)
610
+ });
611
+ }
145
612
  });
146
613
 
147
614
  // Walkthrough end event
148
615
  const walkthroughEndListenerId = mapView.eventsHandler.on('walkthroughEnd', () => {
149
- notifyNative("walkthroughEnd", { timestamp: Date.now() });
616
+ try {
617
+ notifyNative("onWalkthroughEnd", {});
618
+ } catch (error) {
619
+ console.error('Error in walkthroughEnd event handler:', error);
620
+ notifyNative("onError", {
621
+ operation: "walkthroughEnd",
622
+ error: getSimpleErrorMessage(error)
623
+ });
624
+ }
150
625
  });
151
626
 
152
627
  // Store listener IDs for cleanup
@@ -182,36 +657,115 @@
182
657
  // ============================================================================
183
658
 
184
659
  /**
185
- * Initializes the map with site options
660
+ * Initializes the map with site options and enhanced error handling
186
661
  * @param {Object} siteOptions - Site configuration options
187
662
  */
188
- async function init(siteOptions) {
663
+ function init(siteOptions) {
664
+ // Validate input parameters
665
+ if (!siteOptions || typeof siteOptions !== 'object') {
666
+ notifyNative("onError", {
667
+ operation: "init",
668
+ error: "Invalid siteOptions provided"
669
+ });
670
+ return;
671
+ }
672
+
673
+ // Check if already initialized
674
+ if (window._appState === 'ready' && window._mapView) {
675
+ notifyNative("onRenderComplete", {
676
+ site: safeToJSON(window._site)
677
+ });
678
+ return;
679
+ }
680
+
681
+ window._appState = 'initializing';
682
+
189
683
  try {
190
684
  const container = document.getElementById('mapContainer');
191
685
  if (!container) {
192
686
  throw new Error('Map container not found');
193
687
  }
194
688
 
195
- if (!window.becomap?.getSite || !window.becomap?.getMapView) {
196
- throw new Error('Becomap UMD not loaded');
197
- }
198
-
199
- const mapOptions = { zoom: 18.5 };
200
- window._site = await window.becomap.getSite(siteOptions);
201
- window._mapView = await window.becomap.getMapView(container, window._site, mapOptions);
202
-
203
- // Setup event listeners
204
- setupMapViewEventListeners(window._mapView);
689
+ // Check if Becomap UMD is loaded with timeout
690
+ const checkBecomapLoaded = () => {
691
+ return new Promise((resolve, reject) => {
692
+ const maxAttempts = 50; // 5 seconds with 100ms intervals
693
+ let attempts = 0;
694
+
695
+ const checkInterval = setInterval(() => {
696
+ attempts++;
697
+
698
+ if (window.becomap?.getSite && window.becomap?.getMapView) {
699
+ clearInterval(checkInterval);
700
+ resolve();
701
+ } else if (attempts >= maxAttempts) {
702
+ clearInterval(checkInterval);
703
+ reject(new Error('Becomap UMD failed to load within timeout'));
704
+ }
705
+ }, 100);
706
+ });
707
+ };
708
+
709
+ // Initialize with proper error handling and timeouts
710
+ checkBecomapLoaded()
711
+ .then(() => {
712
+ const mapOptions = { zoom: 18.5 };
713
+
714
+ // Add timeout to getSite
715
+ const getSiteWithTimeout = Promise.race([
716
+ window.becomap.getSite(siteOptions),
717
+ new Promise((_, reject) =>
718
+ setTimeout(() => reject(new Error('getSite timeout')), 10000)
719
+ )
720
+ ]);
721
+
722
+ return getSiteWithTimeout;
723
+ })
724
+ .then(site => {
725
+ if (!site) {
726
+ throw new Error('Failed to load site data');
727
+ }
728
+
729
+ window._site = site;
730
+
731
+ // Add timeout to getMapView
732
+ const getMapViewWithTimeout = Promise.race([
733
+ window.becomap.getMapView(container, site, { zoom: 18.5 }),
734
+ new Promise((_, reject) =>
735
+ setTimeout(() => reject(new Error('getMapView timeout')), 15000)
736
+ )
737
+ ]);
738
+
739
+ return getMapViewWithTimeout;
740
+ })
741
+ .then(mapView => {
742
+ if (!mapView) {
743
+ throw new Error('Failed to create map view');
744
+ }
745
+
746
+ window._mapView = mapView;
747
+
748
+ // Setup event listeners
749
+ setupMapViewEventListeners(mapView);
750
+
751
+ // Initialize bridge health monitoring
752
+ updateBridgeHealth(isNativeBridgeAvailable());
753
+ })
754
+ .catch(err => {
755
+ window._appState = 'error';
756
+
757
+ notifyNative("onError", {
758
+ operation: "init",
759
+ error: getSimpleErrorMessage(err)
760
+ });
761
+ });
205
762
 
206
- notifyNative("onRenderComplete", {
207
- site: safeToJSON(window._site),
208
- timestamp: Date.now()
209
- });
210
763
  } catch (err) {
211
- console.error('Init error:', err);
212
- notifyNative("initError", {
213
- message: err.message,
214
- timestamp: Date.now()
764
+ window._appState = 'error';
765
+
766
+ notifyNative("onError", {
767
+ operation: "init",
768
+ error: getSimpleErrorMessage(err)
215
769
  });
216
770
  }
217
771
  }
@@ -222,68 +776,104 @@
222
776
 
223
777
  // Floor and Location Methods
224
778
  globalThis.getCurrentFloor = () => {
225
- const floor = executeWithErrorHandling(
779
+ executeWithErrorHandling(
226
780
  () => window._mapView?.currentFloor,
227
- "getCurrentFloorError"
228
- );
229
- notifyNative("getCurrentFloor", safeToJSON(floor));
781
+ "onError",
782
+ { operation: "getCurrentFloor" }
783
+ ).then(floor => {
784
+ notifyNative("onGetCurrentFloor", safeToJSON(floor));
785
+ }).catch(error => {
786
+ console.error('Error in getCurrentFloor:', error);
787
+ notifyNative("onGetCurrentFloor", null);
788
+ });
230
789
  };
231
790
 
232
791
  globalThis.selectFloorWithId = (floor) => {
233
792
  executeWithErrorHandling(
234
793
  () => window._mapView?.selectFloorWithId(floor),
235
- "selectFloorError"
794
+ "onError",
795
+ { operation: "selectFloorWithId", floor }
236
796
  );
237
797
  };
238
798
 
239
799
  globalThis.selectLocationWithId = (location) => {
240
800
  executeWithErrorHandling(
241
801
  () => window._mapView?.selectLocationWithId(location),
242
- "selectLocationError"
802
+ "onError",
803
+ { operation: "selectLocationWithId", location }
243
804
  );
244
805
  };
245
806
 
246
807
  // Data Retrieval Methods
247
808
  globalThis.getCategories = () => {
248
- const categories = executeWithErrorHandling(
809
+ executeWithErrorHandling(
249
810
  () => window._mapView?.getCategories(),
250
- "getCategoriesError",
811
+ "onError",
812
+ { operation: "getCategories" },
251
813
  []
252
- );
253
- notifyNative("getCategories", categories?.map(cat => safeToJSON(cat)) || []);
814
+ ).then(categories => {
815
+ // Ensure categories is an array before mapping
816
+ const categoriesArray = Array.isArray(categories) ? categories : [];
817
+ notifyNative("onGetCategories", categoriesArray.map(cat => safeToJSON(cat)));
818
+ }).catch(error => {
819
+ console.error('Error in getCategories:', error);
820
+ notifyNative("onGetCategories", []);
821
+ });
254
822
  };
255
823
 
256
824
  globalThis.getLocations = () => {
257
- const locations = executeWithErrorHandling(
825
+ executeWithErrorHandling(
258
826
  () => window._mapView?.getLocations(),
259
- "getLocationsError",
827
+ "onError",
828
+ { operation: "getLocations" },
260
829
  []
261
- );
262
- notifyNative("getLocations", locations?.map(loc => safeToJSON(loc)) || []);
830
+ ).then(locations => {
831
+ // Ensure locations is an array before mapping
832
+ const locationsArray = Array.isArray(locations) ? locations : [];
833
+ notifyNative("onGetLocations", locationsArray.map(loc => safeToJSON(loc)));
834
+ }).catch(error => {
835
+ console.error('Error in getLocations:', error);
836
+ notifyNative("onGetLocations", []);
837
+ });
263
838
  };
264
839
 
265
- globalThis.getAllAmenities = () => {
266
- const amenities = executeWithErrorHandling(
840
+ globalThis.getAmenities = () => {
841
+ executeWithErrorHandling(
267
842
  () => window._mapView?.getAllAminityLocations(),
268
- "getAllAmenitiesError",
843
+ "onError",
844
+ { operation: "getAmenities" },
269
845
  []
270
- );
271
- notifyNative("getAllAmenities", amenities?.map(amenity => safeToJSON(amenity)) || []);
846
+ ).then(amenities => {
847
+ // Ensure amenities is an array before mapping
848
+ const amenitiesArray = Array.isArray(amenities) ? amenities : [];
849
+ notifyNative("onGetAmenities", amenitiesArray.map(amenity => safeToJSON(amenity)));
850
+ }).catch(error => {
851
+ console.error('Error in getAmenities:', error);
852
+ notifyNative("onGetAmenities", []);
853
+ });
272
854
  };
273
855
 
274
- globalThis.getAmenities = () => {
275
- const amenities = executeWithErrorHandling(
856
+ globalThis.getAmenityTypes = () => {
857
+ executeWithErrorHandling(
276
858
  () => window._mapView?.getAmenities(),
277
- "getAmenitiesError",
859
+ "onError",
860
+ { operation: "getAmenityTypes" },
278
861
  []
279
- );
280
- notifyNative("getAmenities", amenities?.map(amenity => safeToJSON(amenity)) || []);
862
+ ).then(amenities => {
863
+ // Ensure amenities is an array before mapping
864
+ const amenitiesArray = Array.isArray(amenities) ? amenities : [];
865
+ notifyNative("onGetAmenityTypes", amenitiesArray.map(amenity => safeToJSON(amenity)));
866
+ }).catch(error => {
867
+ console.error('Error in getAmenityTypes:', error);
868
+ notifyNative("onGetAmenityTypes", []);
869
+ });
281
870
  };
282
871
 
283
872
  globalThis.selectAmenities = (type) => {
284
873
  executeWithErrorHandling(
285
874
  () => window._mapView?.selectAmenities(type),
286
- "selectAmenitiesError"
875
+ "onError",
876
+ { operation: "selectAmenities", type }
287
877
  );
288
878
  };
289
879
 
@@ -291,113 +881,176 @@
291
881
  globalThis.getSessionId = async () => {
292
882
  try {
293
883
  const sessionId = await window._mapView?.getSessionId();
294
- notifyNative("getSessionId", sessionId);
884
+ notifyNative("onGetSessionId", sessionId);
295
885
  } catch (err) {
296
- notifyNative("getSessionIdError", {
297
- message: err.message,
298
- timestamp: Date.now()
886
+ notifyNative("onError", {
887
+ operation: "getSessionId",
888
+ error: getSimpleErrorMessage(err)
299
889
  });
300
890
  }
301
891
  };
302
892
 
303
- globalThis.getQuestions = () => {
304
- const questions = executeWithErrorHandling(
305
- () => window._mapView?.getQuestions(),
306
- "getQuestionsError",
307
- []
308
- );
309
- notifyNative("getQuestions", questions?.map(q => safeToJSON(q)) || []);
310
- };
311
-
312
893
  globalThis.getHappenings = (type) => {
313
- const happenings = executeWithErrorHandling(
894
+ executeWithErrorHandling(
314
895
  () => window._mapView?.getHappenings(type),
315
- "getHappeningsError",
896
+ "onError",
897
+ { operation: "getHappenings", type },
316
898
  []
317
- );
318
- notifyNative("getHappenings", happenings?.map(h => safeToJSON(h)) || []);
899
+ ).then(happenings => {
900
+ // Ensure happenings is an array before mapping
901
+ const happeningsArray = Array.isArray(happenings) ? happenings : [];
902
+ notifyNative("onGetHappenings", happeningsArray.map(h => safeToJSON(h)));
903
+ }).catch(error => {
904
+ console.error('Error in getHappenings:', error);
905
+ notifyNative("onGetHappenings", []);
906
+ });
319
907
  };
320
908
 
321
909
  globalThis.getEventSuggestions = async (sessionId, answers) => {
322
910
  try {
323
911
  const suggestions = await window._mapView?.getEventSuggestions(sessionId, answers);
324
- notifyNative("getEventSuggestions", suggestions?.map(s => safeToJSON(s)) || []);
912
+ notifyNative("onGetEventSuggestions", suggestions?.map(s => safeToJSON(s)) || []);
325
913
  } catch (err) {
326
- notifyNative("getEventSuggestionsError", {
327
- message: err.message,
328
- timestamp: Date.now()
914
+ notifyNative("onError", {
915
+ operation: "getEventSuggestions",
916
+ error: getSimpleErrorMessage(err)
329
917
  });
330
918
  }
331
919
  };
332
920
 
333
921
  // Viewport and Camera Methods
334
- globalThis.focusTo = (location, zoom, bearing, pitch) => {
922
+ globalThis.focusTo = (locationId, zoom, bearing, pitch) => {
923
+ // Validate parameters for mobile SDK
924
+ const validationError = validateMobileParameters(
925
+ { locationId, zoom, bearing, pitch },
926
+ {
927
+ locationId: { required: true, type: 'string' },
928
+ zoom: { type: 'number', range: [1, 25] },
929
+ bearing: { type: 'number', range: [0, 360] },
930
+ pitch: { type: 'number', range: [0, 60] }
931
+ }
932
+ );
933
+
934
+ if (validationError) {
935
+ notifyNative("onError", {
936
+ operation: "focusTo",
937
+ error: validationError
938
+ });
939
+ return;
940
+ }
941
+
942
+ // Get location object from ID using mapView
943
+ if (!window._mapView) {
944
+ notifyNative("onError", {
945
+ operation: "focusTo",
946
+ error: "MapView not initialized"
947
+ });
948
+ return;
949
+ }
950
+
951
+ const locations = window._mapView.getLocations();
952
+ const location = locations?.find(loc => loc.id === locationId);
953
+ if (!location) {
954
+ notifyNative("onError", {
955
+ operation: "focusTo",
956
+ error: `Location not found for ID: ${locationId}`
957
+ });
958
+ return;
959
+ }
960
+
335
961
  executeWithErrorHandling(
336
962
  () => window._mapView?.focusTo(location, zoom, bearing, pitch),
337
- "focusToError"
963
+ "onError",
964
+ { operation: "focusTo" }
338
965
  );
339
966
  };
340
967
 
341
968
  globalThis.clearSelection = () => {
342
969
  executeWithErrorHandling(
343
970
  () => window._mapView?.clearSelection(),
344
- "clearSelectionError"
971
+ "onError",
972
+ { operation: "clearSelection" }
345
973
  );
346
974
  };
347
975
 
348
976
  globalThis.updateZoom = (zoom) => {
349
977
  executeWithErrorHandling(
350
978
  () => window._mapView?.updateZoom(zoom),
351
- "updateZoomError"
979
+ "onError",
980
+ { operation: "updateZoom", zoom }
352
981
  );
353
982
  };
354
983
 
355
984
  globalThis.updatePitch = (pitch) => {
356
985
  executeWithErrorHandling(
357
986
  () => window._mapView?.updatePitch(pitch),
358
- "updatePitchError"
987
+ "onError",
988
+ { operation: "updatePitch", pitch }
359
989
  );
360
990
  };
361
991
 
362
992
  globalThis.updateBearing = (bearing) => {
363
993
  executeWithErrorHandling(
364
994
  () => window._mapView?.updateBearing(bearing),
365
- "updateBearingError"
995
+ "onError",
996
+ { operation: "updateBearing", bearing }
366
997
  );
367
998
  };
368
999
 
369
1000
  globalThis.enableMultiSelection = (val) => {
370
1001
  executeWithErrorHandling(
371
1002
  () => window._mapView?.enableMultiSelection(val),
372
- "enableMultiSelectionError"
1003
+ "onError",
1004
+ { operation: "enableMultiSelection", value: val }
373
1005
  );
374
1006
  };
375
1007
 
376
1008
  globalThis.setBounds = (sw, ne) => {
377
1009
  executeWithErrorHandling(
378
1010
  () => window._mapView?.setBounds(sw, ne),
379
- "setBoundsError"
1011
+ "onError",
1012
+ { operation: "setBounds", southwest: sw, northeast: ne }
380
1013
  );
381
1014
  };
382
1015
 
383
1016
  globalThis.setViewport = (options) => {
384
1017
  executeWithErrorHandling(
385
1018
  () => window._mapView?.setViewport(options),
386
- "setViewportError"
1019
+ "onError",
1020
+ { operation: "setViewport", options }
387
1021
  );
388
1022
  };
389
1023
 
390
1024
  globalThis.resetDefaultViewport = (options) => {
391
1025
  executeWithErrorHandling(
392
1026
  () => window._mapView?.resetDefaultViewport(options),
393
- "resetDefaultViewportError"
1027
+ "onError",
1028
+ { operation: "resetDefaultViewport", options }
394
1029
  );
395
1030
  };
396
1031
 
397
1032
  // Search Methods
398
1033
  globalThis.searchForLocations = (q, callbackId) => {
1034
+ // Validate search parameters
1035
+ const validationError = validateMobileParameters(
1036
+ { q, callbackId },
1037
+ {
1038
+ q: { required: true, type: 'string', maxLength: 100 },
1039
+ callbackId: { required: true, type: 'string' }
1040
+ }
1041
+ );
1042
+
1043
+ if (validationError) {
1044
+ notifyNative("onSearchForLocations", {
1045
+ callbackId,
1046
+ results: [],
1047
+ error: validationError
1048
+ });
1049
+ return;
1050
+ }
1051
+
399
1052
  if (!window._mapView?.searchForLocations) {
400
- notifyNative("searchForLocations", {
1053
+ notifyNative("onSearchForLocations", {
401
1054
  callbackId,
402
1055
  results: [],
403
1056
  error: "Search method not available"
@@ -405,17 +1058,25 @@
405
1058
  return;
406
1059
  }
407
1060
 
408
- window._mapView.searchForLocations(q, (matches) => {
409
- notifyNative("searchForLocations", {
1061
+ try {
1062
+ window._mapView.searchForLocations(q, (matches) => {
1063
+ notifyNative("onSearchForLocations", {
1064
+ callbackId,
1065
+ results: matches?.map(m => safeToJSON(m)) || []
1066
+ });
1067
+ });
1068
+ } catch (error) {
1069
+ notifyNative("onSearchForLocations", {
410
1070
  callbackId,
411
- results: matches?.map(m => safeToJSON(m)) || []
1071
+ results: [],
1072
+ error: getSimpleErrorMessage(error)
412
1073
  });
413
- });
1074
+ }
414
1075
  };
415
1076
 
416
1077
  globalThis.searchForCategories = (q, callbackId) => {
417
1078
  if (!window._mapView?.searchForCategories) {
418
- notifyNative("searchForCategories", {
1079
+ notifyNative("onSearchForCategories", {
419
1080
  callbackId,
420
1081
  results: [],
421
1082
  error: "Search method not available"
@@ -424,7 +1085,7 @@
424
1085
  }
425
1086
 
426
1087
  window._mapView.searchForCategories(q, (matches) => {
427
- notifyNative("searchForCategories", {
1088
+ notifyNative("onSearchForCategories", {
428
1089
  callbackId,
429
1090
  results: matches?.map(m => safeToJSON(m)) || []
430
1091
  });
@@ -436,35 +1097,119 @@
436
1097
  // ============================================================================
437
1098
 
438
1099
  globalThis.getRoute = (startID, goalID, waypoints = [], routeOptions) => {
1100
+ // Parameter validation
1101
+ const validationError = validateRouteParameters(startID, goalID, waypoints, routeOptions);
1102
+ if (validationError) {
1103
+ notifyNative("onError", {
1104
+ operation: "getRoute",
1105
+ error: validationError
1106
+ });
1107
+ return;
1108
+ }
1109
+
1110
+ // // Defensive checks for becomap and getRouteById
1111
+ // if (!window.becomap) {
1112
+ // console.error("getRoute error: window.becomap is undefined", { startID, goalID, waypoints, routeOptions });
1113
+ // notifyNative("onError", {
1114
+ // operation: "getRoute",
1115
+ // error: "window.becomap is not available"
1116
+ // });
1117
+ // return;
1118
+ // }
1119
+ // if (typeof window.becomap.getRouteById !== 'function') {
1120
+ // console.error("getRoute error: window.becomap.getRouteById is not a function", { becomap: window.becomap });
1121
+ // notifyNative("onError", {
1122
+ // operation: "getRoute",
1123
+ // error: "getRouteById is not a function on becomap"
1124
+ // });
1125
+ // return;
1126
+ // }
1127
+
439
1128
  try {
440
1129
  const routes = window.becomap.getRouteById(startID, goalID, waypoints, routeOptions);
441
- notifyNative("getRoute", routes?.map(route => safeToJSON(route)) || []);
1130
+ console.log("getRouteById returned:", routes);
1131
+
1132
+ // Handle no route found scenario
1133
+ if (!routes || !Array.isArray(routes) || routes.length === 0) {
1134
+ notifyNative("onError", {
1135
+ operation: "getRoute",
1136
+ error: "No route found between specified locations",
1137
+ debug: { startID, goalID, waypoints, routeOptions, routes }
1138
+ });
1139
+ return;
1140
+ }
1141
+
1142
+ // No further validation, just return the segments
1143
+ try {
1144
+ const serializedRoutes = routes.map(route => {
1145
+ try {
1146
+ return safeToJSON(route);
1147
+ } catch (serializeError) {
1148
+ console.error("Error serializing route:", serializeError, route);
1149
+ return null;
1150
+ }
1151
+ }).filter(Boolean); // Remove any null entries from failed serialization
1152
+
1153
+ notifyNative("onGetRoute", serializedRoutes);
1154
+ } catch (serializeError) {
1155
+ console.error("Error in route serialization:", serializeError);
1156
+ notifyNative("onError", {
1157
+ operation: "getRoute",
1158
+ error: "Failed to serialize route data",
1159
+ debug: { serializeError: serializeError?.message }
1160
+ });
1161
+ }
442
1162
  } catch (error) {
443
- notifyNative("getRouteError", {
444
- message: error.message,
445
- timestamp: Date.now()
1163
+ console.error("getRoute exception:", error, { startID, goalID, waypoints, routeOptions });
1164
+ notifyNative("onError", {
1165
+ operation: "getRoute",
1166
+ error: getSimpleErrorMessage(error),
1167
+ debug: { startID, goalID, waypoints, routeOptions, error: error?.message }
446
1168
  });
447
1169
  }
448
1170
  };
449
1171
 
450
- globalThis.showRoute = () => {
451
- executeWithErrorHandling(
452
- () => window._mapView?.routeController?.showSavedRoute(),
453
- "showRouteError"
454
- );
1172
+ globalThis.showRoute = (segmentOrderIndex) => {
1173
+ const routeController = window._mapView?.routeController;
1174
+ if (!routeController) {
1175
+ notifyNative("onError", {
1176
+ operation: "showRoute",
1177
+ error: "Route controller not available"
1178
+ });
1179
+ return;
1180
+ }
1181
+
1182
+ try {
1183
+ const validationError = validateSegmentIndex(segmentOrderIndex, routeController);
1184
+ if (validationError) {
1185
+ notifyNative("onError", {
1186
+ operation: "showRoute",
1187
+ error: validationError
1188
+ });
1189
+ return;
1190
+ }
1191
+ routeController.showSegmentByOrderIndex(segmentOrderIndex);
1192
+ } catch (error) {
1193
+ notifyNative("onError", {
1194
+ operation: "showRoute",
1195
+ error: getSimpleErrorMessage(error)
1196
+ });
1197
+ }
455
1198
  };
456
1199
 
457
1200
  globalThis.showStep = (step) => {
458
1201
  executeWithErrorHandling(
459
1202
  () => window._mapView?.routeController?.showStepByOrderIndex(step),
460
- "showStepError"
1203
+ "onError",
1204
+ { operation: "showStep", step }
461
1205
  );
462
1206
  };
463
1207
 
464
1208
  globalThis.clearAllRoutes = () => {
465
1209
  executeWithErrorHandling(
466
1210
  () => window._mapView?.routeController?.clearAllRoutes(),
467
- "clearAllRoutesError"
1211
+ "onError",
1212
+ { operation: "clearAllRoutes" }
468
1213
  );
469
1214
  };
470
1215
 
@@ -473,28 +1218,157 @@
473
1218
  // ============================================================================
474
1219
 
475
1220
  /**
476
- * Cleanup function to be called when the webview is destroyed
1221
+ * Enhanced cleanup function with proper resource management
477
1222
  */
478
1223
  globalThis.cleanup = () => {
479
- clearMapViewEventListeners();
480
- window._mapView = null;
481
- window._site = null;
1224
+ try {
1225
+ window._appState = 'destroyed';
1226
+
1227
+ // Clear event listeners
1228
+ clearMapViewEventListeners();
1229
+
1230
+ // Clear operation queue
1231
+ window._operationQueue = [];
1232
+ window._isProcessingQueue = false;
1233
+
1234
+ // Reset bridge health
1235
+ window._bridgeHealth = {
1236
+ isConnected: false,
1237
+ lastHeartbeat: null,
1238
+ connectionAttempts: 0,
1239
+ maxRetries: 3
1240
+ };
1241
+
1242
+ // Cleanup map view
1243
+ if (window._mapView && typeof window._mapView.destroy === 'function') {
1244
+ window._mapView.destroy();
1245
+ }
1246
+ window._mapView = null;
1247
+
1248
+ // Clear site data
1249
+ window._site = null;
1250
+
1251
+ // Clear any stored failed messages
1252
+ try {
1253
+ localStorage.removeItem('becomap_failed_messages');
1254
+ } catch (e) {
1255
+ console.warn('Failed to clear localStorage:', e);
1256
+ }
1257
+
1258
+ // Final notification to native
1259
+ notifyNative("onCleanupComplete", {}, true); // Skip queue for final message
1260
+
1261
+ } catch (error) {
1262
+ notifyNative("onError", {
1263
+ operation: "cleanup",
1264
+ error: getSimpleErrorMessage(error)
1265
+ }, true);
1266
+ }
482
1267
  };
483
1268
 
484
- // Export init function
485
- globalThis.init = init;
486
- </script>
1269
+ /**
1270
+ * Application state management
1271
+ */
1272
+ globalThis.getAppState = () => {
1273
+ const state = {
1274
+ appState: window._appState,
1275
+ hasMapView: !!window._mapView,
1276
+ hasSite: !!window._site
1277
+ };
1278
+
1279
+ notifyNative("onGetAppState", state);
1280
+ return state;
1281
+ };
487
1282
 
488
- <!-- <script>
489
- const siteOptions = {
490
- clientId: "c079dfa3a77dad13351cfacd95841c2c2780fe08",
491
- clientSecret: "f62a59675b2a47ddb75f1f994d88e653",
492
- siteIdentifier: "67dcf5dd2f21c64e3225254f"
1283
+ /**
1284
+ * Health check function for native to verify bridge connectivity
1285
+ */
1286
+ globalThis.healthCheck = () => {
1287
+ const healthData = {
1288
+ appState: window._appState,
1289
+ bridgeConnected: isNativeBridgeAvailable(),
1290
+ mapViewReady: !!window._mapView,
1291
+ siteLoaded: !!window._site
1292
+ };
1293
+
1294
+ updateBridgeHealth(true); // Update heartbeat
1295
+ notifyNative("onHealthCheck", healthData);
1296
+
1297
+ return healthData;
493
1298
  };
494
- window.addEventListener("DOMContentLoaded", () => {
495
- init(siteOptions);
1299
+
1300
+ /**
1301
+ * Error recovery function
1302
+ */
1303
+ globalThis.recoverFromError = () => {
1304
+ try {
1305
+
1306
+
1307
+ // Reset app state if in error
1308
+ if (window._appState === 'error') {
1309
+ window._appState = 'initializing';
1310
+ }
1311
+
1312
+ // Clear failed operations
1313
+ window._operationQueue = [];
1314
+ window._isProcessingQueue = false;
1315
+
1316
+ // Reset bridge health
1317
+ updateBridgeHealth(isNativeBridgeAvailable());
1318
+
1319
+ // Process any pending operations
1320
+ processOperationQueue();
1321
+
1322
+ notifyNative("onErrorRecovery", {});
1323
+
1324
+ } catch (error) {
1325
+ notifyNative("onError", {
1326
+ operation: "recoverFromError",
1327
+ error: getSimpleErrorMessage(error)
1328
+ });
1329
+ }
1330
+ };
1331
+
1332
+ /**
1333
+ * Debug information function
1334
+ */
1335
+ globalThis.getDebugInfo = () => {
1336
+ const debugInfo = {
1337
+ appState: window._appState,
1338
+ hasMapView: !!window._mapView,
1339
+ hasSite: !!window._site,
1340
+ becomapLoaded: !!(window.becomap?.getSite && window.becomap?.getMapView)
1341
+ };
1342
+
1343
+ notifyNative("onGetDebugInfo", debugInfo);
1344
+ return debugInfo;
1345
+ };
1346
+
1347
+
1348
+
1349
+ /**
1350
+ * Global error handler for unhandled errors
1351
+ */
1352
+ window.addEventListener('error', (event) => {
1353
+ errorDebouncer.debounce('global-error', () => {
1354
+ notifyNative("onError", {
1355
+ operation: "global",
1356
+ error: "Unexpected error occurred"
1357
+ });
1358
+ });
496
1359
  });
497
- </script> -->
498
- </body>
499
1360
 
500
- </html>
1361
+
1362
+ // Initialize bridge health monitoring
1363
+ updateBridgeHealth(isNativeBridgeAvailable());
1364
+
1365
+ // Set up periodic health checks
1366
+ setInterval(() => {
1367
+ if (window._appState !== 'destroyed') {
1368
+ updateBridgeHealth(isNativeBridgeAvailable());
1369
+ }
1370
+ }, 5000); // Check every 5 seconds
1371
+
1372
+ // Export init function
1373
+ globalThis.init = init;</script>
1374
+ </body>