becomap 1.5.70 → 1.5.71
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/lib/becomap.js +1 -1
- package/lib/index.d.ts +21 -12
- package/package.json +1 -1
- package/public/README.md +902 -0
- package/public/index.html +744 -154
package/public/index.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
|
|
4
4
|
<head>
|
|
@@ -22,43 +22,182 @@
|
|
|
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
|
-
*
|
|
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
|
+
console.warn("Native handler not available:", message);
|
|
80
|
+
|
|
81
|
+
if (!skipQueue && window._bridgeHealth.connectionAttempts < window._bridgeHealth.maxRetries) {
|
|
82
|
+
queueOperation(() => notifyNative(type, payload, true));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Fallback: store in localStorage for debugging
|
|
87
|
+
try {
|
|
88
|
+
const failedMessages = JSON.parse(localStorage.getItem('becomap_failed_messages') || '[]');
|
|
89
|
+
failedMessages.push(message);
|
|
90
|
+
localStorage.setItem('becomap_failed_messages', JSON.stringify(failedMessages.slice(-50))); // Keep last 50
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.warn('Failed to store message in localStorage:', e);
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const messageStr = JSON.stringify(message);
|
|
99
|
+
|
|
100
|
+
if (window.webkit?.messageHandlers?.jsHandler) {
|
|
101
|
+
// iOS
|
|
102
|
+
window.webkit.messageHandlers.jsHandler.postMessage(messageStr);
|
|
103
|
+
} else if (window.jsHandler?.postMessage) {
|
|
104
|
+
// Android
|
|
105
|
+
window.jsHandler.postMessage(messageStr);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
updateBridgeHealth(true);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('Failed to send message to native:', error, message);
|
|
111
|
+
updateBridgeHealth(false);
|
|
112
|
+
|
|
113
|
+
if (!skipQueue) {
|
|
114
|
+
queueOperation(() => notifyNative(type, payload, true));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Queues an operation for later execution
|
|
121
|
+
* @param {Function} operation - Operation to queue
|
|
122
|
+
*/
|
|
123
|
+
function queueOperation(operation) {
|
|
124
|
+
window._operationQueue.push({
|
|
125
|
+
operation,
|
|
126
|
+
timestamp: Date.now(),
|
|
127
|
+
retries: 0
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Process queue after a short delay
|
|
131
|
+
setTimeout(processOperationQueue, 100);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Processes queued operations
|
|
47
136
|
*/
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
137
|
+
function processOperationQueue() {
|
|
138
|
+
if (window._isProcessingQueue || !isNativeBridgeAvailable()) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
window._isProcessingQueue = true;
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
const maxAge = 30000; // 30 seconds
|
|
147
|
+
|
|
148
|
+
// Remove expired operations
|
|
149
|
+
window._operationQueue = window._operationQueue.filter(item =>
|
|
150
|
+
now - item.timestamp < maxAge
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Process remaining operations
|
|
154
|
+
while (window._operationQueue.length > 0 && isNativeBridgeAvailable()) {
|
|
155
|
+
const item = window._operationQueue.shift();
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
item.operation();
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error('Error processing queued operation:', error);
|
|
161
|
+
|
|
162
|
+
// Retry failed operations up to 3 times
|
|
163
|
+
if (item.retries < 3) {
|
|
164
|
+
item.retries++;
|
|
165
|
+
window._operationQueue.unshift(item);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} finally {
|
|
171
|
+
window._isProcessingQueue = false;
|
|
59
172
|
}
|
|
60
173
|
}
|
|
61
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Throttle function to prevent event flooding
|
|
177
|
+
* @param {Function} func - Function to throttle
|
|
178
|
+
* @param {number} delay - Delay in milliseconds
|
|
179
|
+
* @returns {Function} - Throttled function
|
|
180
|
+
*/
|
|
181
|
+
function throttle(func, delay) {
|
|
182
|
+
let timeoutId;
|
|
183
|
+
let lastExecTime = 0;
|
|
184
|
+
|
|
185
|
+
return function (...args) {
|
|
186
|
+
const currentTime = Date.now();
|
|
187
|
+
|
|
188
|
+
if (currentTime - lastExecTime > delay) {
|
|
189
|
+
func.apply(this, args);
|
|
190
|
+
lastExecTime = currentTime;
|
|
191
|
+
} else {
|
|
192
|
+
clearTimeout(timeoutId);
|
|
193
|
+
timeoutId = setTimeout(() => {
|
|
194
|
+
func.apply(this, args);
|
|
195
|
+
lastExecTime = Date.now();
|
|
196
|
+
}, delay - (currentTime - lastExecTime));
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
62
201
|
/**
|
|
63
202
|
* Safely converts objects to JSON with error handling
|
|
64
203
|
* @param {any} obj - Object to convert
|
|
@@ -74,20 +213,97 @@
|
|
|
74
213
|
}
|
|
75
214
|
|
|
76
215
|
/**
|
|
77
|
-
* Executes function with error handling and
|
|
216
|
+
* Executes function with enhanced error handling, timeout, and retry mechanism
|
|
78
217
|
* @param {Function} fn - Function to execute
|
|
79
218
|
* @param {string} errorType - Error type for native notification
|
|
219
|
+
* @param {any} errorDetails - Additional error details to include in the payload
|
|
80
220
|
* @param {any} fallbackValue - Fallback value if function fails
|
|
221
|
+
* @param {number} timeout - Timeout in milliseconds (default: 5000)
|
|
222
|
+
* @param {number} maxRetries - Maximum retry attempts (default: 0)
|
|
81
223
|
*/
|
|
82
|
-
function executeWithErrorHandling(fn, errorType, fallbackValue = null) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
224
|
+
function executeWithErrorHandling(fn, errorType, errorDetails = null, fallbackValue = null, timeout = 5000, maxRetries = 0) {
|
|
225
|
+
return new Promise((resolve) => {
|
|
226
|
+
let retryCount = 0;
|
|
227
|
+
|
|
228
|
+
function attemptExecution() {
|
|
229
|
+
try {
|
|
230
|
+
// Validate pre-conditions
|
|
231
|
+
if (window._appState === 'destroyed') {
|
|
232
|
+
throw new Error('Application has been destroyed');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!window._mapView && errorDetails?.operation !== 'init') {
|
|
236
|
+
throw new Error('MapView not initialized');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Set up timeout
|
|
240
|
+
const timeoutId = setTimeout(() => {
|
|
241
|
+
throw new Error(`Operation timed out after ${timeout}ms`);
|
|
242
|
+
}, timeout);
|
|
243
|
+
|
|
244
|
+
const result = fn();
|
|
245
|
+
clearTimeout(timeoutId);
|
|
246
|
+
|
|
247
|
+
// Handle promises
|
|
248
|
+
if (result && typeof result.then === 'function') {
|
|
249
|
+
result
|
|
250
|
+
.then(res => resolve(res !== undefined ? res : fallbackValue))
|
|
251
|
+
.catch(handleError);
|
|
252
|
+
} else {
|
|
253
|
+
resolve(result !== undefined ? result : fallbackValue);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
} catch (error) {
|
|
257
|
+
handleError(error);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function handleError(error) {
|
|
262
|
+
console.error(`${errorType} error (attempt ${retryCount + 1}):`, error);
|
|
263
|
+
|
|
264
|
+
const errorPayload = {
|
|
265
|
+
message: error.message,
|
|
266
|
+
stack: error.stack,
|
|
267
|
+
timestamp: Date.now(),
|
|
268
|
+
attempt: retryCount + 1,
|
|
269
|
+
maxRetries: maxRetries + 1,
|
|
270
|
+
appState: window._appState,
|
|
271
|
+
bridgeHealth: { ...window._bridgeHealth },
|
|
272
|
+
...errorDetails
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// Retry logic for transient errors
|
|
276
|
+
if (retryCount < maxRetries && isRetriableError(error)) {
|
|
277
|
+
retryCount++;
|
|
278
|
+
const delay = Math.min(1000 * Math.pow(2, retryCount - 1), 5000); // Exponential backoff
|
|
279
|
+
setTimeout(attemptExecution, delay);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
notifyNative(errorType, errorPayload);
|
|
284
|
+
resolve(fallbackValue);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
attemptExecution();
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Determines if an error is retriable
|
|
293
|
+
* @param {Error} error - The error to check
|
|
294
|
+
* @returns {boolean} - True if error is retriable
|
|
295
|
+
*/
|
|
296
|
+
function isRetriableError(error) {
|
|
297
|
+
const retriableMessages = [
|
|
298
|
+
'network error',
|
|
299
|
+
'timeout',
|
|
300
|
+
'connection failed',
|
|
301
|
+
'temporary failure'
|
|
302
|
+
];
|
|
303
|
+
|
|
304
|
+
return retriableMessages.some(msg =>
|
|
305
|
+
error.message.toLowerCase().includes(msg)
|
|
306
|
+
);
|
|
91
307
|
}
|
|
92
308
|
|
|
93
309
|
// ============================================================================
|
|
@@ -109,44 +325,100 @@
|
|
|
109
325
|
|
|
110
326
|
// Map load event
|
|
111
327
|
const loadListenerId = mapView.eventsHandler.on('load', () => {
|
|
112
|
-
|
|
328
|
+
try {
|
|
329
|
+
window._appState = 'ready';
|
|
330
|
+
notifyNative("onRenderComplete", {
|
|
331
|
+
site: safeToJSON(window._site),
|
|
332
|
+
timestamp: Date.now(),
|
|
333
|
+
appState: window._appState
|
|
334
|
+
});
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.error('Error in load event handler:', error);
|
|
337
|
+
notifyNative("onError", {
|
|
338
|
+
message: error.message,
|
|
339
|
+
event: 'load',
|
|
340
|
+
timestamp: Date.now()
|
|
341
|
+
});
|
|
342
|
+
}
|
|
113
343
|
});
|
|
114
344
|
|
|
115
|
-
// View change event
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
345
|
+
// View change event (throttled to prevent flooding)
|
|
346
|
+
const throttledViewChange = throttle((args) => {
|
|
347
|
+
try {
|
|
348
|
+
notifyNative("onViewChange", {
|
|
349
|
+
viewOptions: safeToJSON(args.viewOptions),
|
|
350
|
+
timestamp: Date.now()
|
|
351
|
+
});
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error('Error in viewChange event handler:', error);
|
|
354
|
+
}
|
|
355
|
+
}, 100); // Throttle to max 10 events per second
|
|
356
|
+
|
|
357
|
+
const viewChangeListenerId = mapView.eventsHandler.on('viewChange', throttledViewChange);
|
|
122
358
|
|
|
123
359
|
// Location selection event
|
|
124
360
|
const selectListenerId = mapView.eventsHandler.on('select', (args) => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
361
|
+
try {
|
|
362
|
+
notifyNative("onLocationSelect", {
|
|
363
|
+
locations: args.locations?.map(loc => safeToJSON(loc)) || [],
|
|
364
|
+
timestamp: Date.now()
|
|
365
|
+
});
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error('Error in select event handler:', error);
|
|
368
|
+
notifyNative("onError", {
|
|
369
|
+
message: error.message,
|
|
370
|
+
event: 'select',
|
|
371
|
+
timestamp: Date.now()
|
|
372
|
+
});
|
|
373
|
+
}
|
|
129
374
|
});
|
|
130
375
|
|
|
131
376
|
// Floor switch event
|
|
132
377
|
const switchToFloorListenerId = mapView.eventsHandler.on('switchToFloor', (args) => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
378
|
+
try {
|
|
379
|
+
notifyNative("onFloorSwitch", {
|
|
380
|
+
floor: safeToJSON(args.floor),
|
|
381
|
+
timestamp: Date.now()
|
|
382
|
+
});
|
|
383
|
+
} catch (error) {
|
|
384
|
+
console.error('Error in switchToFloor event handler:', error);
|
|
385
|
+
notifyNative("onError", {
|
|
386
|
+
message: error.message,
|
|
387
|
+
event: 'switchToFloor',
|
|
388
|
+
timestamp: Date.now()
|
|
389
|
+
});
|
|
390
|
+
}
|
|
137
391
|
});
|
|
138
392
|
|
|
139
393
|
// Route step load event
|
|
140
394
|
const stepLoadListenerId = mapView.eventsHandler.on('stepLoad', (args) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
395
|
+
try {
|
|
396
|
+
notifyNative("onStepLoad", {
|
|
397
|
+
step: safeToJSON(args.step),
|
|
398
|
+
timestamp: Date.now()
|
|
399
|
+
});
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.error('Error in stepLoad event handler:', error);
|
|
402
|
+
notifyNative("onError", {
|
|
403
|
+
message: error.message,
|
|
404
|
+
event: 'stepLoad',
|
|
405
|
+
timestamp: Date.now()
|
|
406
|
+
});
|
|
407
|
+
}
|
|
145
408
|
});
|
|
146
409
|
|
|
147
410
|
// Walkthrough end event
|
|
148
411
|
const walkthroughEndListenerId = mapView.eventsHandler.on('walkthroughEnd', () => {
|
|
149
|
-
|
|
412
|
+
try {
|
|
413
|
+
notifyNative("onWalkthroughEnd", { timestamp: Date.now() });
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.error('Error in walkthroughEnd event handler:', error);
|
|
416
|
+
notifyNative("onError", {
|
|
417
|
+
message: error.message,
|
|
418
|
+
event: 'walkthroughEnd',
|
|
419
|
+
timestamp: Date.now()
|
|
420
|
+
});
|
|
421
|
+
}
|
|
150
422
|
});
|
|
151
423
|
|
|
152
424
|
// Store listener IDs for cleanup
|
|
@@ -182,36 +454,135 @@
|
|
|
182
454
|
// ============================================================================
|
|
183
455
|
|
|
184
456
|
/**
|
|
185
|
-
* Initializes the map with site options
|
|
457
|
+
* Initializes the map with site options and enhanced error handling
|
|
186
458
|
* @param {Object} siteOptions - Site configuration options
|
|
187
459
|
*/
|
|
188
|
-
|
|
460
|
+
function init(siteOptions) {
|
|
461
|
+
// Validate input parameters
|
|
462
|
+
if (!siteOptions || typeof siteOptions !== 'object') {
|
|
463
|
+
const error = new Error('Invalid siteOptions provided');
|
|
464
|
+
notifyNative("onError", {
|
|
465
|
+
message: error.message,
|
|
466
|
+
timestamp: Date.now(),
|
|
467
|
+
operation: "init",
|
|
468
|
+
siteOptions: siteOptions
|
|
469
|
+
});
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Check if already initialized
|
|
474
|
+
if (window._appState === 'ready' && window._mapView) {
|
|
475
|
+
console.warn('Map already initialized');
|
|
476
|
+
notifyNative("onRenderComplete", {
|
|
477
|
+
site: safeToJSON(window._site),
|
|
478
|
+
timestamp: Date.now(),
|
|
479
|
+
appState: window._appState
|
|
480
|
+
});
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
window._appState = 'initializing';
|
|
485
|
+
|
|
189
486
|
try {
|
|
190
487
|
const container = document.getElementById('mapContainer');
|
|
191
488
|
if (!container) {
|
|
192
489
|
throw new Error('Map container not found');
|
|
193
490
|
}
|
|
194
491
|
|
|
195
|
-
if
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
492
|
+
// Check if Becomap UMD is loaded with timeout
|
|
493
|
+
const checkBecomapLoaded = () => {
|
|
494
|
+
return new Promise((resolve, reject) => {
|
|
495
|
+
const maxAttempts = 50; // 5 seconds with 100ms intervals
|
|
496
|
+
let attempts = 0;
|
|
497
|
+
|
|
498
|
+
const checkInterval = setInterval(() => {
|
|
499
|
+
attempts++;
|
|
500
|
+
|
|
501
|
+
if (window.becomap?.getSite && window.becomap?.getMapView) {
|
|
502
|
+
clearInterval(checkInterval);
|
|
503
|
+
resolve();
|
|
504
|
+
} else if (attempts >= maxAttempts) {
|
|
505
|
+
clearInterval(checkInterval);
|
|
506
|
+
reject(new Error('Becomap UMD failed to load within timeout'));
|
|
507
|
+
}
|
|
508
|
+
}, 100);
|
|
509
|
+
});
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
// Initialize with proper error handling and timeouts
|
|
513
|
+
checkBecomapLoaded()
|
|
514
|
+
.then(() => {
|
|
515
|
+
const mapOptions = { zoom: 18.5 };
|
|
516
|
+
|
|
517
|
+
// Add timeout to getSite
|
|
518
|
+
const getSiteWithTimeout = Promise.race([
|
|
519
|
+
window.becomap.getSite(siteOptions),
|
|
520
|
+
new Promise((_, reject) =>
|
|
521
|
+
setTimeout(() => reject(new Error('getSite timeout')), 10000)
|
|
522
|
+
)
|
|
523
|
+
]);
|
|
524
|
+
|
|
525
|
+
return getSiteWithTimeout;
|
|
526
|
+
})
|
|
527
|
+
.then(site => {
|
|
528
|
+
if (!site) {
|
|
529
|
+
throw new Error('Failed to load site data');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
window._site = site;
|
|
533
|
+
|
|
534
|
+
// Add timeout to getMapView
|
|
535
|
+
const getMapViewWithTimeout = Promise.race([
|
|
536
|
+
window.becomap.getMapView(container, site, { zoom: 18.5 }),
|
|
537
|
+
new Promise((_, reject) =>
|
|
538
|
+
setTimeout(() => reject(new Error('getMapView timeout')), 15000)
|
|
539
|
+
)
|
|
540
|
+
]);
|
|
541
|
+
|
|
542
|
+
return getMapViewWithTimeout;
|
|
543
|
+
})
|
|
544
|
+
.then(mapView => {
|
|
545
|
+
if (!mapView) {
|
|
546
|
+
throw new Error('Failed to create map view');
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
window._mapView = mapView;
|
|
550
|
+
|
|
551
|
+
// Setup event listeners
|
|
552
|
+
setupMapViewEventListeners(mapView);
|
|
553
|
+
|
|
554
|
+
// Initialize bridge health monitoring
|
|
555
|
+
updateBridgeHealth(isNativeBridgeAvailable());
|
|
556
|
+
|
|
557
|
+
console.log('Map initialization completed successfully');
|
|
558
|
+
})
|
|
559
|
+
.catch(err => {
|
|
560
|
+
window._appState = 'error';
|
|
561
|
+
console.error('Init error:', err);
|
|
562
|
+
|
|
563
|
+
notifyNative("onError", {
|
|
564
|
+
message: err.message,
|
|
565
|
+
stack: err.stack,
|
|
566
|
+
timestamp: Date.now(),
|
|
567
|
+
operation: "init",
|
|
568
|
+
siteOptions,
|
|
569
|
+
appState: window._appState,
|
|
570
|
+
containerExists: !!document.getElementById('mapContainer'),
|
|
571
|
+
becomapLoaded: !!(window.becomap?.getSite && window.becomap?.getMapView)
|
|
572
|
+
});
|
|
573
|
+
});
|
|
205
574
|
|
|
206
|
-
notifyNative("onRenderComplete", {
|
|
207
|
-
site: safeToJSON(window._site),
|
|
208
|
-
timestamp: Date.now()
|
|
209
|
-
});
|
|
210
575
|
} catch (err) {
|
|
211
|
-
|
|
212
|
-
|
|
576
|
+
window._appState = 'error';
|
|
577
|
+
console.error('Init synchronous error:', err);
|
|
578
|
+
|
|
579
|
+
notifyNative("onError", {
|
|
213
580
|
message: err.message,
|
|
214
|
-
|
|
581
|
+
stack: err.stack,
|
|
582
|
+
timestamp: Date.now(),
|
|
583
|
+
operation: "init",
|
|
584
|
+
siteOptions,
|
|
585
|
+
appState: window._appState
|
|
215
586
|
});
|
|
216
587
|
}
|
|
217
588
|
}
|
|
@@ -222,68 +593,104 @@
|
|
|
222
593
|
|
|
223
594
|
// Floor and Location Methods
|
|
224
595
|
globalThis.getCurrentFloor = () => {
|
|
225
|
-
|
|
596
|
+
executeWithErrorHandling(
|
|
226
597
|
() => window._mapView?.currentFloor,
|
|
227
|
-
"
|
|
228
|
-
|
|
229
|
-
|
|
598
|
+
"onError",
|
|
599
|
+
{ operation: "getCurrentFloor" }
|
|
600
|
+
).then(floor => {
|
|
601
|
+
notifyNative("onGetCurrentFloor", safeToJSON(floor));
|
|
602
|
+
}).catch(error => {
|
|
603
|
+
console.error('Error in getCurrentFloor:', error);
|
|
604
|
+
notifyNative("onGetCurrentFloor", null);
|
|
605
|
+
});
|
|
230
606
|
};
|
|
231
607
|
|
|
232
608
|
globalThis.selectFloorWithId = (floor) => {
|
|
233
609
|
executeWithErrorHandling(
|
|
234
610
|
() => window._mapView?.selectFloorWithId(floor),
|
|
235
|
-
"
|
|
611
|
+
"onError",
|
|
612
|
+
{ operation: "selectFloorWithId", floor }
|
|
236
613
|
);
|
|
237
614
|
};
|
|
238
615
|
|
|
239
616
|
globalThis.selectLocationWithId = (location) => {
|
|
240
617
|
executeWithErrorHandling(
|
|
241
618
|
() => window._mapView?.selectLocationWithId(location),
|
|
242
|
-
"
|
|
619
|
+
"onError",
|
|
620
|
+
{ operation: "selectLocationWithId", location }
|
|
243
621
|
);
|
|
244
622
|
};
|
|
245
623
|
|
|
246
624
|
// Data Retrieval Methods
|
|
247
625
|
globalThis.getCategories = () => {
|
|
248
|
-
|
|
626
|
+
executeWithErrorHandling(
|
|
249
627
|
() => window._mapView?.getCategories(),
|
|
250
|
-
"
|
|
628
|
+
"onError",
|
|
629
|
+
{ operation: "getCategories" },
|
|
251
630
|
[]
|
|
252
|
-
)
|
|
253
|
-
|
|
631
|
+
).then(categories => {
|
|
632
|
+
// Ensure categories is an array before mapping
|
|
633
|
+
const categoriesArray = Array.isArray(categories) ? categories : [];
|
|
634
|
+
notifyNative("onGetCategories", categoriesArray.map(cat => safeToJSON(cat)));
|
|
635
|
+
}).catch(error => {
|
|
636
|
+
console.error('Error in getCategories:', error);
|
|
637
|
+
notifyNative("onGetCategories", []);
|
|
638
|
+
});
|
|
254
639
|
};
|
|
255
640
|
|
|
256
641
|
globalThis.getLocations = () => {
|
|
257
|
-
|
|
642
|
+
executeWithErrorHandling(
|
|
258
643
|
() => window._mapView?.getLocations(),
|
|
259
|
-
"
|
|
644
|
+
"onError",
|
|
645
|
+
{ operation: "getLocations" },
|
|
260
646
|
[]
|
|
261
|
-
)
|
|
262
|
-
|
|
647
|
+
).then(locations => {
|
|
648
|
+
// Ensure locations is an array before mapping
|
|
649
|
+
const locationsArray = Array.isArray(locations) ? locations : [];
|
|
650
|
+
notifyNative("onGetLocations", locationsArray.map(loc => safeToJSON(loc)));
|
|
651
|
+
}).catch(error => {
|
|
652
|
+
console.error('Error in getLocations:', error);
|
|
653
|
+
notifyNative("onGetLocations", []);
|
|
654
|
+
});
|
|
263
655
|
};
|
|
264
656
|
|
|
265
|
-
globalThis.
|
|
266
|
-
|
|
657
|
+
globalThis.getAmenities = () => {
|
|
658
|
+
executeWithErrorHandling(
|
|
267
659
|
() => window._mapView?.getAllAminityLocations(),
|
|
268
|
-
"
|
|
660
|
+
"onError",
|
|
661
|
+
{ operation: "getAmenities" },
|
|
269
662
|
[]
|
|
270
|
-
)
|
|
271
|
-
|
|
663
|
+
).then(amenities => {
|
|
664
|
+
// Ensure amenities is an array before mapping
|
|
665
|
+
const amenitiesArray = Array.isArray(amenities) ? amenities : [];
|
|
666
|
+
notifyNative("onGetAmenities", amenitiesArray.map(amenity => safeToJSON(amenity)));
|
|
667
|
+
}).catch(error => {
|
|
668
|
+
console.error('Error in getAmenities:', error);
|
|
669
|
+
notifyNative("onGetAmenities", []);
|
|
670
|
+
});
|
|
272
671
|
};
|
|
273
672
|
|
|
274
|
-
globalThis.
|
|
275
|
-
|
|
673
|
+
globalThis.getAmenityTypes = () => {
|
|
674
|
+
executeWithErrorHandling(
|
|
276
675
|
() => window._mapView?.getAmenities(),
|
|
277
|
-
"
|
|
676
|
+
"onError",
|
|
677
|
+
{ operation: "getAmenityTypes" },
|
|
278
678
|
[]
|
|
279
|
-
)
|
|
280
|
-
|
|
679
|
+
).then(amenities => {
|
|
680
|
+
// Ensure amenities is an array before mapping
|
|
681
|
+
const amenitiesArray = Array.isArray(amenities) ? amenities : [];
|
|
682
|
+
notifyNative("onGetAmenityTypes", amenitiesArray.map(amenity => safeToJSON(amenity)));
|
|
683
|
+
}).catch(error => {
|
|
684
|
+
console.error('Error in getAmenityTypes:', error);
|
|
685
|
+
notifyNative("onGetAmenityTypes", []);
|
|
686
|
+
});
|
|
281
687
|
};
|
|
282
688
|
|
|
283
689
|
globalThis.selectAmenities = (type) => {
|
|
284
690
|
executeWithErrorHandling(
|
|
285
691
|
() => window._mapView?.selectAmenities(type),
|
|
286
|
-
"
|
|
692
|
+
"onError",
|
|
693
|
+
{ operation: "selectAmenities", type }
|
|
287
694
|
);
|
|
288
695
|
};
|
|
289
696
|
|
|
@@ -291,41 +698,43 @@
|
|
|
291
698
|
globalThis.getSessionId = async () => {
|
|
292
699
|
try {
|
|
293
700
|
const sessionId = await window._mapView?.getSessionId();
|
|
294
|
-
notifyNative("
|
|
701
|
+
notifyNative("onGetSessionId", sessionId);
|
|
295
702
|
} catch (err) {
|
|
296
|
-
notifyNative("
|
|
703
|
+
notifyNative("onError", {
|
|
297
704
|
message: err.message,
|
|
298
|
-
timestamp: Date.now()
|
|
705
|
+
timestamp: Date.now(),
|
|
706
|
+
operation: "getSessionId"
|
|
299
707
|
});
|
|
300
708
|
}
|
|
301
709
|
};
|
|
302
710
|
|
|
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
711
|
globalThis.getHappenings = (type) => {
|
|
313
|
-
|
|
712
|
+
executeWithErrorHandling(
|
|
314
713
|
() => window._mapView?.getHappenings(type),
|
|
315
|
-
"
|
|
714
|
+
"onError",
|
|
715
|
+
{ operation: "getHappenings", type },
|
|
316
716
|
[]
|
|
317
|
-
)
|
|
318
|
-
|
|
717
|
+
).then(happenings => {
|
|
718
|
+
// Ensure happenings is an array before mapping
|
|
719
|
+
const happeningsArray = Array.isArray(happenings) ? happenings : [];
|
|
720
|
+
notifyNative("onGetHappenings", happeningsArray.map(h => safeToJSON(h)));
|
|
721
|
+
}).catch(error => {
|
|
722
|
+
console.error('Error in getHappenings:', error);
|
|
723
|
+
notifyNative("onGetHappenings", []);
|
|
724
|
+
});
|
|
319
725
|
};
|
|
320
726
|
|
|
321
727
|
globalThis.getEventSuggestions = async (sessionId, answers) => {
|
|
322
728
|
try {
|
|
323
729
|
const suggestions = await window._mapView?.getEventSuggestions(sessionId, answers);
|
|
324
|
-
notifyNative("
|
|
730
|
+
notifyNative("onGetEventSuggestions", suggestions?.map(s => safeToJSON(s)) || []);
|
|
325
731
|
} catch (err) {
|
|
326
|
-
notifyNative("
|
|
732
|
+
notifyNative("onError", {
|
|
327
733
|
message: err.message,
|
|
328
|
-
timestamp: Date.now()
|
|
734
|
+
timestamp: Date.now(),
|
|
735
|
+
operation: "getEventSuggestions",
|
|
736
|
+
sessionId,
|
|
737
|
+
answers
|
|
329
738
|
});
|
|
330
739
|
}
|
|
331
740
|
};
|
|
@@ -334,70 +743,79 @@
|
|
|
334
743
|
globalThis.focusTo = (location, zoom, bearing, pitch) => {
|
|
335
744
|
executeWithErrorHandling(
|
|
336
745
|
() => window._mapView?.focusTo(location, zoom, bearing, pitch),
|
|
337
|
-
"
|
|
746
|
+
"onError",
|
|
747
|
+
{ operation: "focusTo", location, zoom, bearing, pitch }
|
|
338
748
|
);
|
|
339
749
|
};
|
|
340
750
|
|
|
341
751
|
globalThis.clearSelection = () => {
|
|
342
752
|
executeWithErrorHandling(
|
|
343
753
|
() => window._mapView?.clearSelection(),
|
|
344
|
-
"
|
|
754
|
+
"onError",
|
|
755
|
+
{ operation: "clearSelection" }
|
|
345
756
|
);
|
|
346
757
|
};
|
|
347
758
|
|
|
348
759
|
globalThis.updateZoom = (zoom) => {
|
|
349
760
|
executeWithErrorHandling(
|
|
350
761
|
() => window._mapView?.updateZoom(zoom),
|
|
351
|
-
"
|
|
762
|
+
"onError",
|
|
763
|
+
{ operation: "updateZoom", zoom }
|
|
352
764
|
);
|
|
353
765
|
};
|
|
354
766
|
|
|
355
767
|
globalThis.updatePitch = (pitch) => {
|
|
356
768
|
executeWithErrorHandling(
|
|
357
769
|
() => window._mapView?.updatePitch(pitch),
|
|
358
|
-
"
|
|
770
|
+
"onError",
|
|
771
|
+
{ operation: "updatePitch", pitch }
|
|
359
772
|
);
|
|
360
773
|
};
|
|
361
774
|
|
|
362
775
|
globalThis.updateBearing = (bearing) => {
|
|
363
776
|
executeWithErrorHandling(
|
|
364
777
|
() => window._mapView?.updateBearing(bearing),
|
|
365
|
-
"
|
|
778
|
+
"onError",
|
|
779
|
+
{ operation: "updateBearing", bearing }
|
|
366
780
|
);
|
|
367
781
|
};
|
|
368
782
|
|
|
369
783
|
globalThis.enableMultiSelection = (val) => {
|
|
370
784
|
executeWithErrorHandling(
|
|
371
785
|
() => window._mapView?.enableMultiSelection(val),
|
|
372
|
-
"
|
|
786
|
+
"onError",
|
|
787
|
+
{ operation: "enableMultiSelection", value: val }
|
|
373
788
|
);
|
|
374
789
|
};
|
|
375
790
|
|
|
376
791
|
globalThis.setBounds = (sw, ne) => {
|
|
377
792
|
executeWithErrorHandling(
|
|
378
793
|
() => window._mapView?.setBounds(sw, ne),
|
|
379
|
-
"
|
|
794
|
+
"onError",
|
|
795
|
+
{ operation: "setBounds", southwest: sw, northeast: ne }
|
|
380
796
|
);
|
|
381
797
|
};
|
|
382
798
|
|
|
383
799
|
globalThis.setViewport = (options) => {
|
|
384
800
|
executeWithErrorHandling(
|
|
385
801
|
() => window._mapView?.setViewport(options),
|
|
386
|
-
"
|
|
802
|
+
"onError",
|
|
803
|
+
{ operation: "setViewport", options }
|
|
387
804
|
);
|
|
388
805
|
};
|
|
389
806
|
|
|
390
807
|
globalThis.resetDefaultViewport = (options) => {
|
|
391
808
|
executeWithErrorHandling(
|
|
392
809
|
() => window._mapView?.resetDefaultViewport(options),
|
|
393
|
-
"
|
|
810
|
+
"onError",
|
|
811
|
+
{ operation: "resetDefaultViewport", options }
|
|
394
812
|
);
|
|
395
813
|
};
|
|
396
814
|
|
|
397
815
|
// Search Methods
|
|
398
816
|
globalThis.searchForLocations = (q, callbackId) => {
|
|
399
817
|
if (!window._mapView?.searchForLocations) {
|
|
400
|
-
notifyNative("
|
|
818
|
+
notifyNative("onSearchForLocations", {
|
|
401
819
|
callbackId,
|
|
402
820
|
results: [],
|
|
403
821
|
error: "Search method not available"
|
|
@@ -406,7 +824,7 @@
|
|
|
406
824
|
}
|
|
407
825
|
|
|
408
826
|
window._mapView.searchForLocations(q, (matches) => {
|
|
409
|
-
notifyNative("
|
|
827
|
+
notifyNative("onSearchForLocations", {
|
|
410
828
|
callbackId,
|
|
411
829
|
results: matches?.map(m => safeToJSON(m)) || []
|
|
412
830
|
});
|
|
@@ -415,7 +833,7 @@
|
|
|
415
833
|
|
|
416
834
|
globalThis.searchForCategories = (q, callbackId) => {
|
|
417
835
|
if (!window._mapView?.searchForCategories) {
|
|
418
|
-
notifyNative("
|
|
836
|
+
notifyNative("onSearchForCategories", {
|
|
419
837
|
callbackId,
|
|
420
838
|
results: [],
|
|
421
839
|
error: "Search method not available"
|
|
@@ -424,7 +842,7 @@
|
|
|
424
842
|
}
|
|
425
843
|
|
|
426
844
|
window._mapView.searchForCategories(q, (matches) => {
|
|
427
|
-
notifyNative("
|
|
845
|
+
notifyNative("onSearchForCategories", {
|
|
428
846
|
callbackId,
|
|
429
847
|
results: matches?.map(m => safeToJSON(m)) || []
|
|
430
848
|
});
|
|
@@ -438,33 +856,53 @@
|
|
|
438
856
|
globalThis.getRoute = (startID, goalID, waypoints = [], routeOptions) => {
|
|
439
857
|
try {
|
|
440
858
|
const routes = window.becomap.getRouteById(startID, goalID, waypoints, routeOptions);
|
|
441
|
-
notifyNative("
|
|
859
|
+
notifyNative("onGetRoute", routes?.map(route => safeToJSON(route)) || []);
|
|
442
860
|
} catch (error) {
|
|
443
|
-
notifyNative("
|
|
861
|
+
notifyNative("onError", {
|
|
444
862
|
message: error.message,
|
|
445
|
-
timestamp: Date.now()
|
|
863
|
+
timestamp: Date.now(),
|
|
864
|
+
operation: "getRoute",
|
|
865
|
+
startID,
|
|
866
|
+
goalID,
|
|
867
|
+
waypoints,
|
|
868
|
+
routeOptions
|
|
446
869
|
});
|
|
447
870
|
}
|
|
448
871
|
};
|
|
449
872
|
|
|
450
|
-
globalThis.showRoute = () => {
|
|
873
|
+
globalThis.showRoute = (segmentOrderIndex) => {
|
|
451
874
|
executeWithErrorHandling(
|
|
452
|
-
() =>
|
|
453
|
-
|
|
875
|
+
() => {
|
|
876
|
+
const routeController = window._mapView?.routeController;
|
|
877
|
+
if (!routeController) return;
|
|
878
|
+
|
|
879
|
+
if (segmentOrderIndex !== undefined) {
|
|
880
|
+
routeController.showSegmentByOrderIndex(segmentOrderIndex);
|
|
881
|
+
} else {
|
|
882
|
+
const segments = routeController.segments;
|
|
883
|
+
if (segments && segments.length > 0) {
|
|
884
|
+
routeController.showRoute(segments);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
"onError",
|
|
889
|
+
{ operation: "showRoute", segmentOrderIndex }
|
|
454
890
|
);
|
|
455
891
|
};
|
|
456
892
|
|
|
457
893
|
globalThis.showStep = (step) => {
|
|
458
894
|
executeWithErrorHandling(
|
|
459
895
|
() => window._mapView?.routeController?.showStepByOrderIndex(step),
|
|
460
|
-
"
|
|
896
|
+
"onError",
|
|
897
|
+
{ operation: "showStep", step }
|
|
461
898
|
);
|
|
462
899
|
};
|
|
463
900
|
|
|
464
901
|
globalThis.clearAllRoutes = () => {
|
|
465
902
|
executeWithErrorHandling(
|
|
466
903
|
() => window._mapView?.routeController?.clearAllRoutes(),
|
|
467
|
-
"
|
|
904
|
+
"onError",
|
|
905
|
+
{ operation: "clearAllRoutes" }
|
|
468
906
|
);
|
|
469
907
|
};
|
|
470
908
|
|
|
@@ -473,28 +911,180 @@
|
|
|
473
911
|
// ============================================================================
|
|
474
912
|
|
|
475
913
|
/**
|
|
476
|
-
*
|
|
914
|
+
* Enhanced cleanup function with proper resource management
|
|
477
915
|
*/
|
|
478
916
|
globalThis.cleanup = () => {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
917
|
+
try {
|
|
918
|
+
console.log('Starting cleanup process...');
|
|
919
|
+
window._appState = 'destroyed';
|
|
920
|
+
|
|
921
|
+
// Clear event listeners
|
|
922
|
+
clearMapViewEventListeners();
|
|
923
|
+
|
|
924
|
+
// Clear operation queue
|
|
925
|
+
window._operationQueue = [];
|
|
926
|
+
window._isProcessingQueue = false;
|
|
927
|
+
|
|
928
|
+
// Reset bridge health
|
|
929
|
+
window._bridgeHealth = {
|
|
930
|
+
isConnected: false,
|
|
931
|
+
lastHeartbeat: null,
|
|
932
|
+
connectionAttempts: 0,
|
|
933
|
+
maxRetries: 3
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
// Cleanup map view
|
|
937
|
+
if (window._mapView && typeof window._mapView.destroy === 'function') {
|
|
938
|
+
window._mapView.destroy();
|
|
939
|
+
}
|
|
940
|
+
window._mapView = null;
|
|
941
|
+
|
|
942
|
+
// Clear site data
|
|
943
|
+
window._site = null;
|
|
944
|
+
|
|
945
|
+
// Clear any stored failed messages
|
|
946
|
+
try {
|
|
947
|
+
localStorage.removeItem('becomap_failed_messages');
|
|
948
|
+
} catch (e) {
|
|
949
|
+
console.warn('Failed to clear localStorage:', e);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
console.log('Cleanup completed successfully');
|
|
953
|
+
|
|
954
|
+
// Final notification to native
|
|
955
|
+
notifyNative("onCleanupComplete", {
|
|
956
|
+
timestamp: Date.now(),
|
|
957
|
+
appState: window._appState
|
|
958
|
+
}, true); // Skip queue for final message
|
|
959
|
+
|
|
960
|
+
} catch (error) {
|
|
961
|
+
console.error('Error during cleanup:', error);
|
|
962
|
+
notifyNative("onError", {
|
|
963
|
+
message: error.message,
|
|
964
|
+
operation: "cleanup",
|
|
965
|
+
timestamp: Date.now()
|
|
966
|
+
}, true);
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* Application state management
|
|
972
|
+
*/
|
|
973
|
+
globalThis.getAppState = () => {
|
|
974
|
+
const state = {
|
|
975
|
+
appState: window._appState,
|
|
976
|
+
bridgeHealth: { ...window._bridgeHealth },
|
|
977
|
+
queueLength: window._operationQueue.length,
|
|
978
|
+
hasMapView: !!window._mapView,
|
|
979
|
+
hasSite: !!window._site,
|
|
980
|
+
timestamp: Date.now()
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
notifyNative("onGetAppState", state);
|
|
984
|
+
return state;
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Health check function for native to verify bridge connectivity
|
|
989
|
+
*/
|
|
990
|
+
globalThis.healthCheck = () => {
|
|
991
|
+
const healthData = {
|
|
992
|
+
timestamp: Date.now(),
|
|
993
|
+
appState: window._appState,
|
|
994
|
+
bridgeConnected: isNativeBridgeAvailable(),
|
|
995
|
+
mapViewReady: !!window._mapView,
|
|
996
|
+
siteLoaded: !!window._site,
|
|
997
|
+
queueLength: window._operationQueue.length,
|
|
998
|
+
lastHeartbeat: window._bridgeHealth.lastHeartbeat
|
|
999
|
+
};
|
|
1000
|
+
|
|
1001
|
+
updateBridgeHealth(true); // Update heartbeat
|
|
1002
|
+
notifyNative("onHealthCheck", healthData);
|
|
1003
|
+
|
|
1004
|
+
return healthData;
|
|
482
1005
|
};
|
|
483
1006
|
|
|
1007
|
+
/**
|
|
1008
|
+
* Error recovery function
|
|
1009
|
+
*/
|
|
1010
|
+
globalThis.recoverFromError = () => {
|
|
1011
|
+
try {
|
|
1012
|
+
console.log('Attempting error recovery...');
|
|
1013
|
+
|
|
1014
|
+
// Reset app state if in error
|
|
1015
|
+
if (window._appState === 'error') {
|
|
1016
|
+
window._appState = 'initializing';
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Clear failed operations
|
|
1020
|
+
window._operationQueue = [];
|
|
1021
|
+
window._isProcessingQueue = false;
|
|
1022
|
+
|
|
1023
|
+
// Reset bridge health
|
|
1024
|
+
updateBridgeHealth(isNativeBridgeAvailable());
|
|
1025
|
+
|
|
1026
|
+
// Process any pending operations
|
|
1027
|
+
processOperationQueue();
|
|
1028
|
+
|
|
1029
|
+
notifyNative("onErrorRecovery", {
|
|
1030
|
+
timestamp: Date.now(),
|
|
1031
|
+
appState: window._appState,
|
|
1032
|
+
bridgeHealth: { ...window._bridgeHealth }
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
console.log('Error recovery completed');
|
|
1036
|
+
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
console.error('Error during recovery:', error);
|
|
1039
|
+
notifyNative("onError", {
|
|
1040
|
+
message: error.message,
|
|
1041
|
+
operation: "recoverFromError",
|
|
1042
|
+
timestamp: Date.now()
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Debug information function
|
|
1049
|
+
*/
|
|
1050
|
+
globalThis.getDebugInfo = () => {
|
|
1051
|
+
const debugInfo = {
|
|
1052
|
+
timestamp: Date.now(),
|
|
1053
|
+
appState: window._appState,
|
|
1054
|
+
bridgeHealth: { ...window._bridgeHealth },
|
|
1055
|
+
queueLength: window._operationQueue.length,
|
|
1056
|
+
hasMapView: !!window._mapView,
|
|
1057
|
+
hasSite: !!window._site,
|
|
1058
|
+
eventListeners: window._eventListeners.size,
|
|
1059
|
+
userAgent: navigator.userAgent,
|
|
1060
|
+
url: window.location.href,
|
|
1061
|
+
becomapLoaded: !!(window.becomap?.getSite && window.becomap?.getMapView),
|
|
1062
|
+
containerExists: !!document.getElementById('mapContainer')
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
// Get failed messages from localStorage
|
|
1066
|
+
try {
|
|
1067
|
+
const failedMessages = JSON.parse(localStorage.getItem('becomap_failed_messages') || '[]');
|
|
1068
|
+
debugInfo.failedMessagesCount = failedMessages.length;
|
|
1069
|
+
debugInfo.lastFailedMessage = failedMessages[failedMessages.length - 1];
|
|
1070
|
+
} catch (e) {
|
|
1071
|
+
debugInfo.failedMessagesError = e.message;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
notifyNative("onGetDebugInfo", debugInfo);
|
|
1075
|
+
return debugInfo;
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
// Initialize bridge health monitoring
|
|
1079
|
+
updateBridgeHealth(isNativeBridgeAvailable());
|
|
1080
|
+
|
|
1081
|
+
// Set up periodic health checks
|
|
1082
|
+
setInterval(() => {
|
|
1083
|
+
if (window._appState !== 'destroyed') {
|
|
1084
|
+
updateBridgeHealth(isNativeBridgeAvailable());
|
|
1085
|
+
}
|
|
1086
|
+
}, 5000); // Check every 5 seconds
|
|
1087
|
+
|
|
484
1088
|
// Export init function
|
|
485
|
-
globalThis.init = init
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
<!-- <script>
|
|
489
|
-
const siteOptions = {
|
|
490
|
-
clientId: "c079dfa3a77dad13351cfacd95841c2c2780fe08",
|
|
491
|
-
clientSecret: "f62a59675b2a47ddb75f1f994d88e653",
|
|
492
|
-
siteIdentifier: "67dcf5dd2f21c64e3225254f"
|
|
493
|
-
};
|
|
494
|
-
window.addEventListener("DOMContentLoaded", () => {
|
|
495
|
-
init(siteOptions);
|
|
496
|
-
});
|
|
497
|
-
</script> -->
|
|
498
|
-
</body>
|
|
499
|
-
|
|
500
|
-
</html>
|
|
1089
|
+
globalThis.init = init;</script>
|
|
1090
|
+
</body>
|