becomap 1.5.69 → 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/README.md +15 -15
- package/lib/becomap.js +1 -1
- package/lib/index.d.ts +38 -75
- package/package.json +71 -72
- package/public/README.md +902 -0
- package/public/index.html +1090 -0
- package/webpack.umd.config.js +91 -87
- package/public/index1.html +0 -52
|
@@ -0,0 +1,1090 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
7
|
+
<title>Becomap UMD SDK</title>
|
|
8
|
+
<style>
|
|
9
|
+
body,
|
|
10
|
+
html {
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
height: 100%;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#mapContainer {
|
|
17
|
+
width: 100%;
|
|
18
|
+
height: 100%;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
|
|
23
|
+
<body>
|
|
24
|
+
<div id="mapContainer"></div>
|
|
25
|
+
<script src="https://unpkg.com/maplibre-gl@4.4.1/dist/maplibre-gl.js"></script>
|
|
26
|
+
<script src="https://unpkg.com/@turf/turf@7.1.0/turf.min.js"></script>
|
|
27
|
+
<script>// ============================================================================
|
|
28
|
+
// GLOBAL VARIABLES
|
|
29
|
+
// ============================================================================
|
|
30
|
+
window._mapView = null;
|
|
31
|
+
window._site = null;
|
|
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'
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// UTILITY FUNCTIONS
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
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
|
|
71
|
+
* @param {string} type - Message type
|
|
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
|
|
136
|
+
*/
|
|
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;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
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
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Safely converts objects to JSON with error handling
|
|
203
|
+
* @param {any} obj - Object to convert
|
|
204
|
+
* @returns {any} - JSON object or original object if conversion fails
|
|
205
|
+
*/
|
|
206
|
+
function safeToJSON(obj) {
|
|
207
|
+
try {
|
|
208
|
+
return obj?.toJSON ? obj.toJSON() : obj;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.warn('Error converting to JSON:', error);
|
|
211
|
+
return obj;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Executes function with enhanced error handling, timeout, and retry mechanism
|
|
217
|
+
* @param {Function} fn - Function to execute
|
|
218
|
+
* @param {string} errorType - Error type for native notification
|
|
219
|
+
* @param {any} errorDetails - Additional error details to include in the payload
|
|
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)
|
|
223
|
+
*/
|
|
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
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ============================================================================
|
|
310
|
+
// BCMapViewEvents CALLBACKS
|
|
311
|
+
// ============================================================================
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Sets up event listeners for BCMapViewEvents
|
|
315
|
+
* @param {Object} mapView - The map view instance
|
|
316
|
+
*/
|
|
317
|
+
function setupMapViewEventListeners(mapView) {
|
|
318
|
+
if (!mapView || !mapView.eventsHandler) {
|
|
319
|
+
console.warn('MapView does not support event listeners');
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Clear existing listeners
|
|
324
|
+
clearMapViewEventListeners();
|
|
325
|
+
|
|
326
|
+
// Map load event
|
|
327
|
+
const loadListenerId = mapView.eventsHandler.on('load', () => {
|
|
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
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
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);
|
|
358
|
+
|
|
359
|
+
// Location selection event
|
|
360
|
+
const selectListenerId = mapView.eventsHandler.on('select', (args) => {
|
|
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
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Floor switch event
|
|
377
|
+
const switchToFloorListenerId = mapView.eventsHandler.on('switchToFloor', (args) => {
|
|
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
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Route step load event
|
|
394
|
+
const stepLoadListenerId = mapView.eventsHandler.on('stepLoad', (args) => {
|
|
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
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// Walkthrough end event
|
|
411
|
+
const walkthroughEndListenerId = mapView.eventsHandler.on('walkthroughEnd', () => {
|
|
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
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Store listener IDs for cleanup
|
|
425
|
+
window._eventListeners.set('load', loadListenerId);
|
|
426
|
+
window._eventListeners.set('viewChange', viewChangeListenerId);
|
|
427
|
+
window._eventListeners.set('select', selectListenerId);
|
|
428
|
+
window._eventListeners.set('switchToFloor', switchToFloorListenerId);
|
|
429
|
+
window._eventListeners.set('stepLoad', stepLoadListenerId);
|
|
430
|
+
window._eventListeners.set('walkthroughEnd', walkthroughEndListenerId);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Clears all map view event listeners
|
|
435
|
+
*/
|
|
436
|
+
function clearMapViewEventListeners() {
|
|
437
|
+
if (!window._mapView || !window._mapView.eventsHandler) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
window._eventListeners.forEach((listenerId, eventName) => {
|
|
442
|
+
try {
|
|
443
|
+
window._mapView.eventsHandler.off(eventName, listenerId);
|
|
444
|
+
} catch (error) {
|
|
445
|
+
console.warn(`Error removing ${eventName} listener:`, error);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
window._eventListeners.clear();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ============================================================================
|
|
453
|
+
// INITIALIZATION
|
|
454
|
+
// ============================================================================
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Initializes the map with site options and enhanced error handling
|
|
458
|
+
* @param {Object} siteOptions - Site configuration options
|
|
459
|
+
*/
|
|
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
|
+
|
|
486
|
+
try {
|
|
487
|
+
const container = document.getElementById('mapContainer');
|
|
488
|
+
if (!container) {
|
|
489
|
+
throw new Error('Map container not found');
|
|
490
|
+
}
|
|
491
|
+
|
|
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
|
+
});
|
|
574
|
+
|
|
575
|
+
} catch (err) {
|
|
576
|
+
window._appState = 'error';
|
|
577
|
+
console.error('Init synchronous error:', err);
|
|
578
|
+
|
|
579
|
+
notifyNative("onError", {
|
|
580
|
+
message: err.message,
|
|
581
|
+
stack: err.stack,
|
|
582
|
+
timestamp: Date.now(),
|
|
583
|
+
operation: "init",
|
|
584
|
+
siteOptions,
|
|
585
|
+
appState: window._appState
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// ============================================================================
|
|
591
|
+
// MAP VIEW METHODS
|
|
592
|
+
// ============================================================================
|
|
593
|
+
|
|
594
|
+
// Floor and Location Methods
|
|
595
|
+
globalThis.getCurrentFloor = () => {
|
|
596
|
+
executeWithErrorHandling(
|
|
597
|
+
() => window._mapView?.currentFloor,
|
|
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
|
+
});
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
globalThis.selectFloorWithId = (floor) => {
|
|
609
|
+
executeWithErrorHandling(
|
|
610
|
+
() => window._mapView?.selectFloorWithId(floor),
|
|
611
|
+
"onError",
|
|
612
|
+
{ operation: "selectFloorWithId", floor }
|
|
613
|
+
);
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
globalThis.selectLocationWithId = (location) => {
|
|
617
|
+
executeWithErrorHandling(
|
|
618
|
+
() => window._mapView?.selectLocationWithId(location),
|
|
619
|
+
"onError",
|
|
620
|
+
{ operation: "selectLocationWithId", location }
|
|
621
|
+
);
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
// Data Retrieval Methods
|
|
625
|
+
globalThis.getCategories = () => {
|
|
626
|
+
executeWithErrorHandling(
|
|
627
|
+
() => window._mapView?.getCategories(),
|
|
628
|
+
"onError",
|
|
629
|
+
{ operation: "getCategories" },
|
|
630
|
+
[]
|
|
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
|
+
});
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
globalThis.getLocations = () => {
|
|
642
|
+
executeWithErrorHandling(
|
|
643
|
+
() => window._mapView?.getLocations(),
|
|
644
|
+
"onError",
|
|
645
|
+
{ operation: "getLocations" },
|
|
646
|
+
[]
|
|
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
|
+
});
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
globalThis.getAmenities = () => {
|
|
658
|
+
executeWithErrorHandling(
|
|
659
|
+
() => window._mapView?.getAllAminityLocations(),
|
|
660
|
+
"onError",
|
|
661
|
+
{ operation: "getAmenities" },
|
|
662
|
+
[]
|
|
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
|
+
});
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
globalThis.getAmenityTypes = () => {
|
|
674
|
+
executeWithErrorHandling(
|
|
675
|
+
() => window._mapView?.getAmenities(),
|
|
676
|
+
"onError",
|
|
677
|
+
{ operation: "getAmenityTypes" },
|
|
678
|
+
[]
|
|
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
|
+
});
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
globalThis.selectAmenities = (type) => {
|
|
690
|
+
executeWithErrorHandling(
|
|
691
|
+
() => window._mapView?.selectAmenities(type),
|
|
692
|
+
"onError",
|
|
693
|
+
{ operation: "selectAmenities", type }
|
|
694
|
+
);
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
// Session and Event Methods
|
|
698
|
+
globalThis.getSessionId = async () => {
|
|
699
|
+
try {
|
|
700
|
+
const sessionId = await window._mapView?.getSessionId();
|
|
701
|
+
notifyNative("onGetSessionId", sessionId);
|
|
702
|
+
} catch (err) {
|
|
703
|
+
notifyNative("onError", {
|
|
704
|
+
message: err.message,
|
|
705
|
+
timestamp: Date.now(),
|
|
706
|
+
operation: "getSessionId"
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
globalThis.getHappenings = (type) => {
|
|
712
|
+
executeWithErrorHandling(
|
|
713
|
+
() => window._mapView?.getHappenings(type),
|
|
714
|
+
"onError",
|
|
715
|
+
{ operation: "getHappenings", type },
|
|
716
|
+
[]
|
|
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
|
+
});
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
globalThis.getEventSuggestions = async (sessionId, answers) => {
|
|
728
|
+
try {
|
|
729
|
+
const suggestions = await window._mapView?.getEventSuggestions(sessionId, answers);
|
|
730
|
+
notifyNative("onGetEventSuggestions", suggestions?.map(s => safeToJSON(s)) || []);
|
|
731
|
+
} catch (err) {
|
|
732
|
+
notifyNative("onError", {
|
|
733
|
+
message: err.message,
|
|
734
|
+
timestamp: Date.now(),
|
|
735
|
+
operation: "getEventSuggestions",
|
|
736
|
+
sessionId,
|
|
737
|
+
answers
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
// Viewport and Camera Methods
|
|
743
|
+
globalThis.focusTo = (location, zoom, bearing, pitch) => {
|
|
744
|
+
executeWithErrorHandling(
|
|
745
|
+
() => window._mapView?.focusTo(location, zoom, bearing, pitch),
|
|
746
|
+
"onError",
|
|
747
|
+
{ operation: "focusTo", location, zoom, bearing, pitch }
|
|
748
|
+
);
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
globalThis.clearSelection = () => {
|
|
752
|
+
executeWithErrorHandling(
|
|
753
|
+
() => window._mapView?.clearSelection(),
|
|
754
|
+
"onError",
|
|
755
|
+
{ operation: "clearSelection" }
|
|
756
|
+
);
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
globalThis.updateZoom = (zoom) => {
|
|
760
|
+
executeWithErrorHandling(
|
|
761
|
+
() => window._mapView?.updateZoom(zoom),
|
|
762
|
+
"onError",
|
|
763
|
+
{ operation: "updateZoom", zoom }
|
|
764
|
+
);
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
globalThis.updatePitch = (pitch) => {
|
|
768
|
+
executeWithErrorHandling(
|
|
769
|
+
() => window._mapView?.updatePitch(pitch),
|
|
770
|
+
"onError",
|
|
771
|
+
{ operation: "updatePitch", pitch }
|
|
772
|
+
);
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
globalThis.updateBearing = (bearing) => {
|
|
776
|
+
executeWithErrorHandling(
|
|
777
|
+
() => window._mapView?.updateBearing(bearing),
|
|
778
|
+
"onError",
|
|
779
|
+
{ operation: "updateBearing", bearing }
|
|
780
|
+
);
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
globalThis.enableMultiSelection = (val) => {
|
|
784
|
+
executeWithErrorHandling(
|
|
785
|
+
() => window._mapView?.enableMultiSelection(val),
|
|
786
|
+
"onError",
|
|
787
|
+
{ operation: "enableMultiSelection", value: val }
|
|
788
|
+
);
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
globalThis.setBounds = (sw, ne) => {
|
|
792
|
+
executeWithErrorHandling(
|
|
793
|
+
() => window._mapView?.setBounds(sw, ne),
|
|
794
|
+
"onError",
|
|
795
|
+
{ operation: "setBounds", southwest: sw, northeast: ne }
|
|
796
|
+
);
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
globalThis.setViewport = (options) => {
|
|
800
|
+
executeWithErrorHandling(
|
|
801
|
+
() => window._mapView?.setViewport(options),
|
|
802
|
+
"onError",
|
|
803
|
+
{ operation: "setViewport", options }
|
|
804
|
+
);
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
globalThis.resetDefaultViewport = (options) => {
|
|
808
|
+
executeWithErrorHandling(
|
|
809
|
+
() => window._mapView?.resetDefaultViewport(options),
|
|
810
|
+
"onError",
|
|
811
|
+
{ operation: "resetDefaultViewport", options }
|
|
812
|
+
);
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
// Search Methods
|
|
816
|
+
globalThis.searchForLocations = (q, callbackId) => {
|
|
817
|
+
if (!window._mapView?.searchForLocations) {
|
|
818
|
+
notifyNative("onSearchForLocations", {
|
|
819
|
+
callbackId,
|
|
820
|
+
results: [],
|
|
821
|
+
error: "Search method not available"
|
|
822
|
+
});
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
window._mapView.searchForLocations(q, (matches) => {
|
|
827
|
+
notifyNative("onSearchForLocations", {
|
|
828
|
+
callbackId,
|
|
829
|
+
results: matches?.map(m => safeToJSON(m)) || []
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
globalThis.searchForCategories = (q, callbackId) => {
|
|
835
|
+
if (!window._mapView?.searchForCategories) {
|
|
836
|
+
notifyNative("onSearchForCategories", {
|
|
837
|
+
callbackId,
|
|
838
|
+
results: [],
|
|
839
|
+
error: "Search method not available"
|
|
840
|
+
});
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
window._mapView.searchForCategories(q, (matches) => {
|
|
845
|
+
notifyNative("onSearchForCategories", {
|
|
846
|
+
callbackId,
|
|
847
|
+
results: matches?.map(m => safeToJSON(m)) || []
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
// ============================================================================
|
|
853
|
+
// ROUTE CONTROLLER METHODS
|
|
854
|
+
// ============================================================================
|
|
855
|
+
|
|
856
|
+
globalThis.getRoute = (startID, goalID, waypoints = [], routeOptions) => {
|
|
857
|
+
try {
|
|
858
|
+
const routes = window.becomap.getRouteById(startID, goalID, waypoints, routeOptions);
|
|
859
|
+
notifyNative("onGetRoute", routes?.map(route => safeToJSON(route)) || []);
|
|
860
|
+
} catch (error) {
|
|
861
|
+
notifyNative("onError", {
|
|
862
|
+
message: error.message,
|
|
863
|
+
timestamp: Date.now(),
|
|
864
|
+
operation: "getRoute",
|
|
865
|
+
startID,
|
|
866
|
+
goalID,
|
|
867
|
+
waypoints,
|
|
868
|
+
routeOptions
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
globalThis.showRoute = (segmentOrderIndex) => {
|
|
874
|
+
executeWithErrorHandling(
|
|
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 }
|
|
890
|
+
);
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
globalThis.showStep = (step) => {
|
|
894
|
+
executeWithErrorHandling(
|
|
895
|
+
() => window._mapView?.routeController?.showStepByOrderIndex(step),
|
|
896
|
+
"onError",
|
|
897
|
+
{ operation: "showStep", step }
|
|
898
|
+
);
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
globalThis.clearAllRoutes = () => {
|
|
902
|
+
executeWithErrorHandling(
|
|
903
|
+
() => window._mapView?.routeController?.clearAllRoutes(),
|
|
904
|
+
"onError",
|
|
905
|
+
{ operation: "clearAllRoutes" }
|
|
906
|
+
);
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
// ============================================================================
|
|
910
|
+
// CLEANUP AND EXPORTS
|
|
911
|
+
// ============================================================================
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Enhanced cleanup function with proper resource management
|
|
915
|
+
*/
|
|
916
|
+
globalThis.cleanup = () => {
|
|
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;
|
|
1005
|
+
};
|
|
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
|
+
|
|
1088
|
+
// Export init function
|
|
1089
|
+
globalThis.init = init;</script>
|
|
1090
|
+
</body>
|