@fias/plugin-dev-harness 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge/live-handler.d.ts +5 -1
- package/dist/bridge/live-handler.d.ts.map +1 -1
- package/dist/bridge/live-handler.js +67 -0
- package/dist/bridge/live-handler.js.map +1 -1
- package/dist/bridge/mock-handler.d.ts +2 -1
- package/dist/bridge/mock-handler.d.ts.map +1 -1
- package/dist/bridge/mock-handler.js +23 -0
- package/dist/bridge/mock-handler.js.map +1 -1
- package/dist/bridge/types.d.ts +2 -0
- package/dist/bridge/types.d.ts.map +1 -1
- package/dist/server/harness-server.d.ts.map +1 -1
- package/dist/server/harness-server.js +42 -2
- package/dist/server/harness-server.js.map +1 -1
- package/dist/server/static/harness.css +59 -1
- package/dist/server/static/harness.html +9 -5
- package/dist/server/static/harness.js +195 -39
- package/package.json +1 -1
- package/dist/server/static/static/harness.css +0 -390
- package/dist/server/static/static/harness.html +0 -57
- package/dist/server/static/static/harness.js +0 -603
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fias Arche Dev — Client-side Bridge Host
|
|
3
3
|
*
|
|
4
|
-
* Ported from the production PluginBridgeHost
|
|
5
|
-
* (client/src/arche/components/PluginBridge.ts).
|
|
6
|
-
*
|
|
7
4
|
* Handles postMessage communication with the plugin iframe,
|
|
8
5
|
* enforcing permissions and rate limits, and proxying server-side
|
|
9
6
|
* operations through the harness Express server.
|
|
@@ -22,14 +19,17 @@
|
|
|
22
19
|
var pluginStatus = document.getElementById('plugin-status');
|
|
23
20
|
var themeBadge = document.getElementById('theme-badge');
|
|
24
21
|
var loginModal = document.getElementById('login-modal');
|
|
25
|
-
var
|
|
22
|
+
var loginEmail = document.getElementById('login-email');
|
|
23
|
+
var loginPassword = document.getElementById('login-password');
|
|
26
24
|
var loginError = document.getElementById('login-error');
|
|
27
25
|
var loginSubmit = document.getElementById('login-submit');
|
|
28
26
|
var loginCancel = document.getElementById('login-cancel');
|
|
27
|
+
var envSelector = document.getElementById('env-selector');
|
|
29
28
|
|
|
30
29
|
var messageCount = 0;
|
|
31
30
|
var currentTheme = 'dark';
|
|
32
31
|
var currentMode = 'mock';
|
|
32
|
+
var currentEnvironment = 'staging';
|
|
33
33
|
var hasCredentials = false;
|
|
34
34
|
var cachedConfig = null;
|
|
35
35
|
|
|
@@ -62,8 +62,10 @@
|
|
|
62
62
|
cachedConfig = config;
|
|
63
63
|
currentTheme = config.mockTheme || 'dark';
|
|
64
64
|
currentMode = config.mode || 'mock';
|
|
65
|
+
currentEnvironment = config.environment || 'staging';
|
|
65
66
|
hasCredentials = config.hasCredentials || false;
|
|
66
67
|
|
|
68
|
+
envSelector.value = currentEnvironment;
|
|
67
69
|
updateThemeBadge();
|
|
68
70
|
updateModeBadge();
|
|
69
71
|
|
|
@@ -72,7 +74,6 @@
|
|
|
72
74
|
fetchCredits();
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
// Check if plugin server is reachable, then load
|
|
76
77
|
checkPluginReachable(config.pluginUrl, function (reachable) {
|
|
77
78
|
if (reachable) {
|
|
78
79
|
pluginStatus.classList.add('hidden');
|
|
@@ -94,7 +95,6 @@
|
|
|
94
95
|
|
|
95
96
|
reloadBtn.addEventListener('click', function () {
|
|
96
97
|
if (cachedConfig) {
|
|
97
|
-
// Re-check reachability on reload
|
|
98
98
|
pluginStatus.classList.remove('hidden', 'error');
|
|
99
99
|
pluginStatus.innerHTML =
|
|
100
100
|
'<div class="status-spinner"></div><p>Connecting to plugin server...</p>';
|
|
@@ -124,10 +124,9 @@
|
|
|
124
124
|
logMessage('send', 'theme_update', { mode: currentTheme });
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
-
// Mode toggle
|
|
127
|
+
// Mode toggle
|
|
128
128
|
modeBadge.addEventListener('click', function () {
|
|
129
129
|
if (currentMode === 'mock') {
|
|
130
|
-
// Switching to live — need credentials
|
|
131
130
|
if (!hasCredentials) {
|
|
132
131
|
showLoginModal();
|
|
133
132
|
return;
|
|
@@ -138,6 +137,42 @@
|
|
|
138
137
|
}
|
|
139
138
|
});
|
|
140
139
|
|
|
140
|
+
// Environment selector
|
|
141
|
+
envSelector.addEventListener('change', function () {
|
|
142
|
+
fetch('/api/environment', {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: { 'Content-Type': 'application/json' },
|
|
145
|
+
body: JSON.stringify({ environment: envSelector.value }),
|
|
146
|
+
})
|
|
147
|
+
.then(function (r) {
|
|
148
|
+
return r.json();
|
|
149
|
+
})
|
|
150
|
+
.then(function (data) {
|
|
151
|
+
currentEnvironment = data.environment;
|
|
152
|
+
currentMode = data.mode;
|
|
153
|
+
hasCredentials = data.hasCredentials;
|
|
154
|
+
updateModeBadge();
|
|
155
|
+
|
|
156
|
+
if (currentMode === 'live') {
|
|
157
|
+
creditBalance.style.display = 'inline';
|
|
158
|
+
fetchCredits();
|
|
159
|
+
} else {
|
|
160
|
+
creditBalance.style.display = 'none';
|
|
161
|
+
creditBalance.textContent = '';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
logMessage(
|
|
165
|
+
'info',
|
|
166
|
+
'Environment: ' +
|
|
167
|
+
currentEnvironment.toUpperCase() +
|
|
168
|
+
(hasCredentials ? '' : ' (not authenticated)'),
|
|
169
|
+
);
|
|
170
|
+
})
|
|
171
|
+
.catch(function (err) {
|
|
172
|
+
logMessage('error', err.message);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
141
176
|
function switchMode(newMode) {
|
|
142
177
|
fetch('/api/mode', {
|
|
143
178
|
method: 'POST',
|
|
@@ -174,17 +209,21 @@
|
|
|
174
209
|
// Login Modal
|
|
175
210
|
// ────────────────────────────────────────────────────────────────
|
|
176
211
|
|
|
212
|
+
var loginTarget = document.getElementById('login-target');
|
|
213
|
+
|
|
177
214
|
function showLoginModal() {
|
|
178
215
|
loginModal.style.display = 'flex';
|
|
179
|
-
|
|
216
|
+
loginEmail.value = '';
|
|
217
|
+
loginPassword.value = '';
|
|
180
218
|
loginError.style.display = 'none';
|
|
181
|
-
|
|
219
|
+
loginSubmit.disabled = false;
|
|
220
|
+
loginSubmit.textContent = 'Sign in';
|
|
221
|
+
loginTarget.textContent = currentEnvironment === 'production' ? 'fias.io' : 'staging.fias.io';
|
|
222
|
+
loginEmail.focus();
|
|
182
223
|
}
|
|
183
224
|
|
|
184
225
|
function hideLoginModal() {
|
|
185
226
|
loginModal.style.display = 'none';
|
|
186
|
-
loginInput.value = '';
|
|
187
|
-
loginError.style.display = 'none';
|
|
188
227
|
}
|
|
189
228
|
|
|
190
229
|
loginCancel.addEventListener('click', hideLoginModal);
|
|
@@ -193,7 +232,12 @@
|
|
|
193
232
|
if (e.target === loginModal) hideLoginModal();
|
|
194
233
|
});
|
|
195
234
|
|
|
196
|
-
|
|
235
|
+
loginEmail.addEventListener('keydown', function (e) {
|
|
236
|
+
if (e.key === 'Enter') loginPassword.focus();
|
|
237
|
+
if (e.key === 'Escape') hideLoginModal();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
loginPassword.addEventListener('keydown', function (e) {
|
|
197
241
|
if (e.key === 'Enter') submitLogin();
|
|
198
242
|
if (e.key === 'Escape') hideLoginModal();
|
|
199
243
|
});
|
|
@@ -201,25 +245,28 @@
|
|
|
201
245
|
loginSubmit.addEventListener('click', submitLogin);
|
|
202
246
|
|
|
203
247
|
function submitLogin() {
|
|
204
|
-
var
|
|
205
|
-
|
|
206
|
-
|
|
248
|
+
var email = loginEmail.value.trim();
|
|
249
|
+
var password = loginPassword.value;
|
|
250
|
+
|
|
251
|
+
if (!email || !password) {
|
|
252
|
+
loginError.textContent = 'Email and password are required.';
|
|
207
253
|
loginError.style.display = 'block';
|
|
208
254
|
return;
|
|
209
255
|
}
|
|
210
256
|
|
|
211
257
|
loginSubmit.disabled = true;
|
|
212
|
-
loginSubmit.textContent = '
|
|
258
|
+
loginSubmit.textContent = 'Signing in...';
|
|
259
|
+
loginError.style.display = 'none';
|
|
213
260
|
|
|
214
|
-
fetch('/api/login', {
|
|
261
|
+
fetch('/api/auth/login', {
|
|
215
262
|
method: 'POST',
|
|
216
263
|
headers: { 'Content-Type': 'application/json' },
|
|
217
|
-
body: JSON.stringify({
|
|
264
|
+
body: JSON.stringify({ email: email, password: password }),
|
|
218
265
|
})
|
|
219
266
|
.then(function (r) {
|
|
220
267
|
if (!r.ok) {
|
|
221
268
|
return r.json().then(function (err) {
|
|
222
|
-
throw new Error(err.error || '
|
|
269
|
+
throw new Error(err.error || 'Sign in failed');
|
|
223
270
|
});
|
|
224
271
|
}
|
|
225
272
|
return r.json();
|
|
@@ -227,8 +274,7 @@
|
|
|
227
274
|
.then(function () {
|
|
228
275
|
hasCredentials = true;
|
|
229
276
|
hideLoginModal();
|
|
230
|
-
logMessage('info', '
|
|
231
|
-
// Automatically switch to live mode after login
|
|
277
|
+
logMessage('info', 'Signed in for ' + currentEnvironment.toUpperCase());
|
|
232
278
|
switchMode('live');
|
|
233
279
|
})
|
|
234
280
|
.catch(function (err) {
|
|
@@ -237,7 +283,7 @@
|
|
|
237
283
|
})
|
|
238
284
|
.finally(function () {
|
|
239
285
|
loginSubmit.disabled = false;
|
|
240
|
-
loginSubmit.textContent = '
|
|
286
|
+
loginSubmit.textContent = 'Sign in';
|
|
241
287
|
});
|
|
242
288
|
}
|
|
243
289
|
|
|
@@ -253,7 +299,6 @@
|
|
|
253
299
|
|
|
254
300
|
logMessage('recv', data.type, data.payload);
|
|
255
301
|
|
|
256
|
-
// Fire-and-forget messages
|
|
257
302
|
if (data.type === 'ready') return;
|
|
258
303
|
|
|
259
304
|
if (data.type === 'resize') {
|
|
@@ -281,19 +326,15 @@
|
|
|
281
326
|
return;
|
|
282
327
|
}
|
|
283
328
|
|
|
284
|
-
// Request/response messages — enforce permissions + rate limits, then proxy
|
|
285
329
|
handleRequest(data);
|
|
286
330
|
});
|
|
287
331
|
|
|
288
332
|
function handleRequest(data) {
|
|
289
333
|
try {
|
|
290
|
-
// Check permissions
|
|
291
334
|
var requiredPerm = PERMISSION_MAP[data.type];
|
|
292
335
|
if (requiredPerm && cachedConfig && cachedConfig.permissions.indexOf(requiredPerm) === -1) {
|
|
293
336
|
throw new Error('Permission denied: ' + requiredPerm + ' not granted');
|
|
294
337
|
}
|
|
295
|
-
|
|
296
|
-
// Check rate limit
|
|
297
338
|
checkRateLimit(data.type);
|
|
298
339
|
} catch (err) {
|
|
299
340
|
logMessage('error', err.message);
|
|
@@ -306,7 +347,12 @@
|
|
|
306
347
|
return;
|
|
307
348
|
}
|
|
308
349
|
|
|
309
|
-
//
|
|
350
|
+
// Streaming path for entity_invoke with stream: true
|
|
351
|
+
if (data.type === 'entity_invoke' && data.payload && data.payload.stream) {
|
|
352
|
+
handleStreamingRequest(data);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
310
356
|
fetch('/api/bridge', {
|
|
311
357
|
method: 'POST',
|
|
312
358
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -323,7 +369,6 @@
|
|
|
323
369
|
.then(function (result) {
|
|
324
370
|
logMessage('send', 'response', result);
|
|
325
371
|
|
|
326
|
-
// Show cost for entity invocations in live mode
|
|
327
372
|
if (
|
|
328
373
|
data.type === 'entity_invoke' &&
|
|
329
374
|
result.metadata &&
|
|
@@ -350,6 +395,86 @@
|
|
|
350
395
|
});
|
|
351
396
|
}
|
|
352
397
|
|
|
398
|
+
function handleStreamingRequest(data) {
|
|
399
|
+
fetch('/api/bridge/stream', {
|
|
400
|
+
method: 'POST',
|
|
401
|
+
headers: { 'Content-Type': 'application/json' },
|
|
402
|
+
body: JSON.stringify({ type: data.type, payload: data.payload }),
|
|
403
|
+
})
|
|
404
|
+
.then(function (response) {
|
|
405
|
+
if (!response.ok) {
|
|
406
|
+
return response.json().then(function (err) {
|
|
407
|
+
throw new Error(err.error || 'Bridge streaming call failed');
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
var contentType = response.headers.get('content-type') || '';
|
|
412
|
+
if (contentType.indexOf('text/event-stream') === -1) {
|
|
413
|
+
// Fallback: not SSE, parse as JSON
|
|
414
|
+
return response.json().then(function (result) {
|
|
415
|
+
sendToPlugin({ type: 'response', messageId: data.messageId, payload: result });
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Parse SSE stream
|
|
420
|
+
var reader = response.body.getReader();
|
|
421
|
+
var decoder = new TextDecoder();
|
|
422
|
+
var buffer = '';
|
|
423
|
+
var finalPayload = null;
|
|
424
|
+
|
|
425
|
+
function pump() {
|
|
426
|
+
return reader.read().then(function (chunk) {
|
|
427
|
+
if (chunk.done) {
|
|
428
|
+
if (finalPayload) {
|
|
429
|
+
logMessage('send', 'response', finalPayload);
|
|
430
|
+
sendToPlugin({ type: 'response', messageId: data.messageId, payload: finalPayload });
|
|
431
|
+
}
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
436
|
+
var lines = buffer.split('\n');
|
|
437
|
+
buffer = lines.pop() || '';
|
|
438
|
+
|
|
439
|
+
for (var i = 0; i < lines.length; i++) {
|
|
440
|
+
var line = lines[i];
|
|
441
|
+
if (line.indexOf('data: ') !== 0) continue;
|
|
442
|
+
try {
|
|
443
|
+
var parsed = JSON.parse(line.slice(6));
|
|
444
|
+
if (parsed.error) throw new Error(parsed.error);
|
|
445
|
+
if (parsed.text) {
|
|
446
|
+
sendToPlugin({ type: 'stream_token', messageId: data.messageId, text: parsed.text });
|
|
447
|
+
}
|
|
448
|
+
if (parsed.done) {
|
|
449
|
+
finalPayload = { output: parsed.output, metadata: parsed.metadata };
|
|
450
|
+
if (parsed.metadata && parsed.metadata.cost > 0) {
|
|
451
|
+
logMessage('cost', 'Credits used: ' + parsed.metadata.cost.toFixed(4));
|
|
452
|
+
fetchCredits();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
} catch (e) {
|
|
456
|
+
if (e instanceof SyntaxError) continue;
|
|
457
|
+
throw e;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return pump();
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return pump();
|
|
466
|
+
})
|
|
467
|
+
.catch(function (err) {
|
|
468
|
+
logMessage('error', err.message);
|
|
469
|
+
sendToPlugin({
|
|
470
|
+
type: 'response',
|
|
471
|
+
messageId: data.messageId,
|
|
472
|
+
payload: null,
|
|
473
|
+
error: err.message,
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
353
478
|
// ────────────────────────────────────────────────────────────────
|
|
354
479
|
// Iframe Init
|
|
355
480
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -385,11 +510,26 @@
|
|
|
385
510
|
themeBadge.textContent = currentTheme.toUpperCase();
|
|
386
511
|
themeBadge.className = 'theme-badge theme-' + currentTheme;
|
|
387
512
|
document.body.style.background = currentTheme === 'light' ? '#ffffff' : '#0a0a0a';
|
|
513
|
+
|
|
514
|
+
// Update toolbar to match theme
|
|
515
|
+
var toolbar = document.querySelector('.toolbar');
|
|
516
|
+
var logo = document.querySelector('.logo');
|
|
517
|
+
var icons = document.querySelectorAll('.btn-icon');
|
|
518
|
+
var env = document.getElementById('env-selector');
|
|
519
|
+
if (currentTheme === 'light') {
|
|
520
|
+
if (toolbar) { toolbar.style.background = '#f5f5f5'; toolbar.style.borderBottomColor = '#e5e5e5'; }
|
|
521
|
+
if (logo) { logo.style.color = '#171717'; }
|
|
522
|
+
if (env) { env.style.background = '#e5e5e5'; env.style.color = '#171717'; env.style.borderColor = '#d4d4d4'; }
|
|
523
|
+
icons.forEach(function (btn) { btn.style.background = '#e5e5e5'; btn.style.color = '#171717'; btn.style.borderColor = '#d4d4d4'; });
|
|
524
|
+
} else {
|
|
525
|
+
if (toolbar) { toolbar.style.background = '#18181b'; toolbar.style.borderBottomColor = '#3f3f46'; }
|
|
526
|
+
if (logo) { logo.style.color = '#e4e4e7'; }
|
|
527
|
+
if (env) { env.style.background = '#27272a'; env.style.color = '#a1a1aa'; env.style.borderColor = '#3f3f46'; }
|
|
528
|
+
icons.forEach(function (btn) { btn.style.background = '#27272a'; btn.style.color = '#e4e4e7'; btn.style.borderColor = '#3f3f46'; });
|
|
529
|
+
}
|
|
388
530
|
}
|
|
389
531
|
|
|
390
532
|
function sendToPlugin(message) {
|
|
391
|
-
// targetOrigin '*' because the plugin may be on a different port.
|
|
392
|
-
// Security is enforced by checking event.source on incoming messages.
|
|
393
533
|
iframe.contentWindow && iframe.contentWindow.postMessage(message, '*');
|
|
394
534
|
}
|
|
395
535
|
|
|
@@ -419,32 +559,51 @@
|
|
|
419
559
|
var SPACING = { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px' };
|
|
420
560
|
var FONTS = { body: SYSTEM_FONTS, heading: SYSTEM_FONTS, mono: MONO_FONTS };
|
|
421
561
|
|
|
562
|
+
var COMPONENTS = {
|
|
563
|
+
borderRadius: '0.5rem',
|
|
564
|
+
buttonRadius: '0.375rem',
|
|
565
|
+
cardRadius: '0.5rem',
|
|
566
|
+
inputRadius: '0.375rem',
|
|
567
|
+
shadowSm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
|
568
|
+
shadowMd: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
|
|
569
|
+
shadowLg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)',
|
|
570
|
+
borderWidth: '1px',
|
|
571
|
+
};
|
|
572
|
+
|
|
422
573
|
function getTheme() {
|
|
423
574
|
if (currentTheme === 'light') {
|
|
424
575
|
return {
|
|
425
576
|
mode: 'light',
|
|
426
577
|
colors: {
|
|
427
|
-
primary: '#171717',
|
|
578
|
+
primary: '#171717', primaryText: '#ffffff',
|
|
579
|
+
secondary: '#e5e5e5', accent: '#2563eb',
|
|
428
580
|
background: '#ffffff', surface: '#fafafa',
|
|
581
|
+
card: '#ffffff', cardText: '#0a0a0a',
|
|
429
582
|
text: '#0a0a0a', textSecondary: '#737373',
|
|
583
|
+
muted: '#f5f5f5', mutedText: '#a3a3a3',
|
|
430
584
|
border: '#e5e5e5',
|
|
431
|
-
error: '#dc2626', warning: '#d97706', success: '#16a34a',
|
|
585
|
+
error: '#dc2626', warning: '#d97706', success: '#16a34a', info: '#2563eb',
|
|
432
586
|
},
|
|
433
587
|
spacing: SPACING,
|
|
434
588
|
fonts: FONTS,
|
|
589
|
+
components: COMPONENTS,
|
|
435
590
|
};
|
|
436
591
|
}
|
|
437
592
|
return {
|
|
438
593
|
mode: 'dark',
|
|
439
594
|
colors: {
|
|
440
|
-
primary: '#ffffff',
|
|
595
|
+
primary: '#ffffff', primaryText: '#0a0a0a',
|
|
596
|
+
secondary: '#1f1f1f', accent: '#3b82f6',
|
|
441
597
|
background: '#0a0a0a', surface: '#171717',
|
|
598
|
+
card: '#141414', cardText: '#ffffff',
|
|
442
599
|
text: '#ffffff', textSecondary: '#a6a6a6',
|
|
600
|
+
muted: '#1e1e1e', mutedText: '#737373',
|
|
443
601
|
border: '#2e2e2e',
|
|
444
|
-
error: '#ef4444', warning: '#f59e0b', success: '#22c55e',
|
|
602
|
+
error: '#ef4444', warning: '#f59e0b', success: '#22c55e', info: '#3b82f6',
|
|
445
603
|
},
|
|
446
604
|
spacing: SPACING,
|
|
447
605
|
fonts: FONTS,
|
|
606
|
+
components: COMPONENTS,
|
|
448
607
|
};
|
|
449
608
|
}
|
|
450
609
|
|
|
@@ -510,7 +669,6 @@
|
|
|
510
669
|
function checkPluginReachable(url, callback) {
|
|
511
670
|
var done = false;
|
|
512
671
|
|
|
513
|
-
// Try fetching through the harness server to avoid CORS issues
|
|
514
672
|
fetch('/api/check-plugin')
|
|
515
673
|
.then(function (r) { return r.json(); })
|
|
516
674
|
.then(function (data) {
|
|
@@ -522,12 +680,10 @@
|
|
|
522
680
|
.catch(function () {
|
|
523
681
|
if (!done) {
|
|
524
682
|
done = true;
|
|
525
|
-
// If the check endpoint doesn't exist, assume reachable and let iframe handle it
|
|
526
683
|
callback(true);
|
|
527
684
|
}
|
|
528
685
|
});
|
|
529
686
|
|
|
530
|
-
// Timeout after 5 seconds
|
|
531
687
|
setTimeout(function () {
|
|
532
688
|
if (!done) {
|
|
533
689
|
done = true;
|