@fias/plugin-dev-harness 1.1.0 → 1.1.2
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/config/config-loader.test.js.map +1 -1
- package/dist/mocks/entity-responses.test.js.map +1 -1
- package/dist/mocks/theme.d.ts +21 -5
- package/dist/mocks/theme.d.ts.map +1 -1
- package/dist/mocks/theme.js +24 -9
- package/dist/mocks/theme.js.map +1 -1
- package/dist/mocks/theme.test.js +25 -9
- package/dist/mocks/theme.test.js.map +1 -1
- package/dist/server/harness-server.d.ts.map +1 -1
- package/dist/server/harness-server.js +278 -210
- package/dist/server/harness-server.js.map +1 -1
- package/dist/server/harness-server.test.js +5 -6
- package/dist/server/harness-server.test.js.map +1 -1
- package/dist/server/static/harness.css +181 -9
- package/dist/server/static/harness.html +24 -5
- package/dist/server/static/harness.js +238 -28
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Fias Arche Dev — Client-side Bridge Host
|
|
3
3
|
*
|
|
4
4
|
* Ported from the production PluginBridgeHost
|
|
5
5
|
* (client/src/arche/components/PluginBridge.ts).
|
|
@@ -19,10 +19,19 @@
|
|
|
19
19
|
var themeToggle = document.getElementById('theme-toggle');
|
|
20
20
|
var reloadBtn = document.getElementById('reload-btn');
|
|
21
21
|
var modeBadge = document.getElementById('mode-badge');
|
|
22
|
-
var
|
|
22
|
+
var pluginUrlEl = document.getElementById('plugin-url');
|
|
23
|
+
var pluginStatus = document.getElementById('plugin-status');
|
|
24
|
+
var themeBadge = document.getElementById('theme-badge');
|
|
25
|
+
var loginModal = document.getElementById('login-modal');
|
|
26
|
+
var loginInput = document.getElementById('login-input');
|
|
27
|
+
var loginError = document.getElementById('login-error');
|
|
28
|
+
var loginSubmit = document.getElementById('login-submit');
|
|
29
|
+
var loginCancel = document.getElementById('login-cancel');
|
|
23
30
|
|
|
24
31
|
var messageCount = 0;
|
|
25
32
|
var currentTheme = 'dark';
|
|
33
|
+
var currentMode = 'mock';
|
|
34
|
+
var hasCredentials = false;
|
|
26
35
|
var cachedConfig = null;
|
|
27
36
|
|
|
28
37
|
/** Permission requirements per bridge call type (matches production) */
|
|
@@ -53,24 +62,29 @@
|
|
|
53
62
|
fetchConfig().then(function (config) {
|
|
54
63
|
cachedConfig = config;
|
|
55
64
|
currentTheme = config.mockTheme || 'dark';
|
|
65
|
+
currentMode = config.mode || 'mock';
|
|
66
|
+
hasCredentials = config.hasCredentials || false;
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
modeBadge.className = 'mode-badge ' + (config.isLive ? 'mode-live' : 'mode-mock');
|
|
68
|
+
updateThemeBadge();
|
|
69
|
+
updateModeBadge();
|
|
60
70
|
|
|
61
|
-
|
|
62
|
-
.map(function (p) {
|
|
63
|
-
return '<span class="perm-badge">' + escapeHtml(p) + '</span>';
|
|
64
|
-
})
|
|
65
|
-
.join('');
|
|
71
|
+
pluginUrlEl.textContent = config.pluginUrl;
|
|
66
72
|
|
|
67
|
-
if (
|
|
73
|
+
if (currentMode === 'live') {
|
|
68
74
|
creditBalance.style.display = 'inline';
|
|
69
75
|
fetchCredits();
|
|
70
76
|
}
|
|
71
77
|
|
|
72
|
-
//
|
|
73
|
-
|
|
78
|
+
// Check if plugin server is reachable, then load
|
|
79
|
+
checkPluginReachable(config.pluginUrl, function (reachable) {
|
|
80
|
+
if (reachable) {
|
|
81
|
+
pluginStatus.classList.add('hidden');
|
|
82
|
+
iframe.classList.remove('hidden');
|
|
83
|
+
iframe.src = config.pluginUrl;
|
|
84
|
+
} else {
|
|
85
|
+
showPluginError(config.pluginUrl);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
74
88
|
});
|
|
75
89
|
|
|
76
90
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -82,11 +96,29 @@
|
|
|
82
96
|
});
|
|
83
97
|
|
|
84
98
|
reloadBtn.addEventListener('click', function () {
|
|
85
|
-
|
|
99
|
+
if (cachedConfig) {
|
|
100
|
+
// Re-check reachability on reload
|
|
101
|
+
pluginStatus.classList.remove('hidden', 'error');
|
|
102
|
+
pluginStatus.innerHTML =
|
|
103
|
+
'<div class="status-spinner"></div><p>Connecting to plugin server...</p>';
|
|
104
|
+
iframe.classList.add('hidden');
|
|
105
|
+
checkPluginReachable(cachedConfig.pluginUrl, function (reachable) {
|
|
106
|
+
if (reachable) {
|
|
107
|
+
pluginStatus.classList.add('hidden');
|
|
108
|
+
iframe.classList.remove('hidden');
|
|
109
|
+
iframe.src = cachedConfig.pluginUrl;
|
|
110
|
+
} else {
|
|
111
|
+
showPluginError(cachedConfig.pluginUrl);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
iframe.src = iframe.src;
|
|
116
|
+
}
|
|
86
117
|
});
|
|
87
118
|
|
|
88
119
|
themeToggle.addEventListener('click', function () {
|
|
89
120
|
currentTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
121
|
+
updateThemeBadge();
|
|
90
122
|
sendToPlugin({
|
|
91
123
|
type: 'theme_update',
|
|
92
124
|
messageId: 'theme_' + Date.now(),
|
|
@@ -95,6 +127,123 @@
|
|
|
95
127
|
logMessage('send', 'theme_update', { mode: currentTheme });
|
|
96
128
|
});
|
|
97
129
|
|
|
130
|
+
// Mode toggle — click the badge to switch between mock/live
|
|
131
|
+
modeBadge.addEventListener('click', function () {
|
|
132
|
+
if (currentMode === 'mock') {
|
|
133
|
+
// Switching to live — need credentials
|
|
134
|
+
if (!hasCredentials) {
|
|
135
|
+
showLoginModal();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
switchMode('live');
|
|
139
|
+
} else {
|
|
140
|
+
switchMode('mock');
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
function switchMode(newMode) {
|
|
145
|
+
fetch('/api/mode', {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
headers: { 'Content-Type': 'application/json' },
|
|
148
|
+
body: JSON.stringify({ mode: newMode }),
|
|
149
|
+
})
|
|
150
|
+
.then(function (r) {
|
|
151
|
+
if (!r.ok) {
|
|
152
|
+
return r.json().then(function (err) {
|
|
153
|
+
throw new Error(err.error || 'Failed to switch mode');
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return r.json();
|
|
157
|
+
})
|
|
158
|
+
.then(function (data) {
|
|
159
|
+
currentMode = data.mode;
|
|
160
|
+
updateModeBadge();
|
|
161
|
+
logMessage('info', 'Mode switched to ' + currentMode.toUpperCase());
|
|
162
|
+
|
|
163
|
+
if (currentMode === 'live') {
|
|
164
|
+
creditBalance.style.display = 'inline';
|
|
165
|
+
fetchCredits();
|
|
166
|
+
} else {
|
|
167
|
+
creditBalance.style.display = 'none';
|
|
168
|
+
creditBalance.textContent = '';
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
.catch(function (err) {
|
|
172
|
+
logMessage('error', err.message);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ────────────────────────────────────────────────────────────────
|
|
177
|
+
// Login Modal
|
|
178
|
+
// ────────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
function showLoginModal() {
|
|
181
|
+
loginModal.style.display = 'flex';
|
|
182
|
+
loginInput.value = '';
|
|
183
|
+
loginError.style.display = 'none';
|
|
184
|
+
loginInput.focus();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function hideLoginModal() {
|
|
188
|
+
loginModal.style.display = 'none';
|
|
189
|
+
loginInput.value = '';
|
|
190
|
+
loginError.style.display = 'none';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
loginCancel.addEventListener('click', hideLoginModal);
|
|
194
|
+
|
|
195
|
+
loginModal.addEventListener('click', function (e) {
|
|
196
|
+
if (e.target === loginModal) hideLoginModal();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
loginInput.addEventListener('keydown', function (e) {
|
|
200
|
+
if (e.key === 'Enter') submitLogin();
|
|
201
|
+
if (e.key === 'Escape') hideLoginModal();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
loginSubmit.addEventListener('click', submitLogin);
|
|
205
|
+
|
|
206
|
+
function submitLogin() {
|
|
207
|
+
var apiKey = loginInput.value.trim();
|
|
208
|
+
if (!apiKey) {
|
|
209
|
+
loginError.textContent = 'Please enter an API key.';
|
|
210
|
+
loginError.style.display = 'block';
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
loginSubmit.disabled = true;
|
|
215
|
+
loginSubmit.textContent = 'Connecting...';
|
|
216
|
+
|
|
217
|
+
fetch('/api/login', {
|
|
218
|
+
method: 'POST',
|
|
219
|
+
headers: { 'Content-Type': 'application/json' },
|
|
220
|
+
body: JSON.stringify({ apiKey: apiKey }),
|
|
221
|
+
})
|
|
222
|
+
.then(function (r) {
|
|
223
|
+
if (!r.ok) {
|
|
224
|
+
return r.json().then(function (err) {
|
|
225
|
+
throw new Error(err.error || 'Login failed');
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return r.json();
|
|
229
|
+
})
|
|
230
|
+
.then(function () {
|
|
231
|
+
hasCredentials = true;
|
|
232
|
+
hideLoginModal();
|
|
233
|
+
logMessage('info', 'API key saved successfully');
|
|
234
|
+
// Automatically switch to live mode after login
|
|
235
|
+
switchMode('live');
|
|
236
|
+
})
|
|
237
|
+
.catch(function (err) {
|
|
238
|
+
loginError.textContent = err.message;
|
|
239
|
+
loginError.style.display = 'block';
|
|
240
|
+
})
|
|
241
|
+
.finally(function () {
|
|
242
|
+
loginSubmit.disabled = false;
|
|
243
|
+
loginSubmit.textContent = 'Save & Connect';
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
98
247
|
// ────────────────────────────────────────────────────────────────
|
|
99
248
|
// Message Handling
|
|
100
249
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -228,8 +377,23 @@
|
|
|
228
377
|
// Helpers
|
|
229
378
|
// ────────────────────────────────────────────────────────────────
|
|
230
379
|
|
|
380
|
+
function updateModeBadge() {
|
|
381
|
+
modeBadge.textContent = currentMode.toUpperCase();
|
|
382
|
+
modeBadge.className = 'mode-badge ' + (currentMode === 'live' ? 'mode-live' : 'mode-mock');
|
|
383
|
+
modeBadge.title = currentMode === 'live'
|
|
384
|
+
? 'Live mode (real AI) — click to switch to mock'
|
|
385
|
+
: hasCredentials
|
|
386
|
+
? 'Mock mode — click to switch to live'
|
|
387
|
+
: 'Mock mode — click to login and enable live mode';
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function updateThemeBadge() {
|
|
391
|
+
themeBadge.textContent = currentTheme.toUpperCase();
|
|
392
|
+
themeBadge.className = 'theme-badge theme-' + currentTheme;
|
|
393
|
+
}
|
|
394
|
+
|
|
231
395
|
function sendToPlugin(message) {
|
|
232
|
-
// targetOrigin '*' because
|
|
396
|
+
// targetOrigin '*' because the plugin may be on a different port.
|
|
233
397
|
// Security is enforced by checking event.source on incoming messages.
|
|
234
398
|
iframe.contentWindow && iframe.contentWindow.postMessage(message, '*');
|
|
235
399
|
}
|
|
@@ -255,32 +419,37 @@
|
|
|
255
419
|
bucket.count++;
|
|
256
420
|
}
|
|
257
421
|
|
|
422
|
+
var SYSTEM_FONTS = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
|
423
|
+
var MONO_FONTS = '"SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace';
|
|
424
|
+
var SPACING = { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '32px' };
|
|
425
|
+
var FONTS = { body: SYSTEM_FONTS, heading: SYSTEM_FONTS, mono: MONO_FONTS };
|
|
426
|
+
|
|
258
427
|
function getTheme() {
|
|
259
428
|
if (currentTheme === 'light') {
|
|
260
429
|
return {
|
|
261
430
|
mode: 'light',
|
|
262
431
|
colors: {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
secondary: '#ede9fe',
|
|
267
|
-
accent: '#8b5cf6',
|
|
268
|
-
muted: '#f4f4f5',
|
|
432
|
+
primary: '#7c3aed', secondary: '#ede9fe',
|
|
433
|
+
background: '#ffffff', surface: '#f4f4f5',
|
|
434
|
+
text: '#18181b', textSecondary: '#71717a',
|
|
269
435
|
border: '#e4e4e7',
|
|
436
|
+
error: '#ef4444', warning: '#f59e0b', success: '#22c55e',
|
|
270
437
|
},
|
|
438
|
+
spacing: SPACING,
|
|
439
|
+
fonts: FONTS,
|
|
271
440
|
};
|
|
272
441
|
}
|
|
273
442
|
return {
|
|
274
443
|
mode: 'dark',
|
|
275
444
|
colors: {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
secondary: '#1e1b4b',
|
|
280
|
-
accent: '#8b5cf6',
|
|
281
|
-
muted: '#27272a',
|
|
445
|
+
primary: '#6d28d9', secondary: '#1e1b4b',
|
|
446
|
+
background: '#0a0a0f', surface: '#27272a',
|
|
447
|
+
text: '#e4e4e7', textSecondary: '#a1a1aa',
|
|
282
448
|
border: '#3f3f46',
|
|
449
|
+
error: '#ef4444', warning: '#f59e0b', success: '#22c55e',
|
|
283
450
|
},
|
|
451
|
+
spacing: SPACING,
|
|
452
|
+
fonts: FONTS,
|
|
284
453
|
};
|
|
285
454
|
}
|
|
286
455
|
|
|
@@ -296,7 +465,7 @@
|
|
|
296
465
|
return r.json();
|
|
297
466
|
})
|
|
298
467
|
.then(function (data) {
|
|
299
|
-
if (data.balance !== undefined && isFinite(data.balance)) {
|
|
468
|
+
if (data.balance !== undefined && data.balance !== null && isFinite(data.balance)) {
|
|
300
469
|
creditBalance.textContent = 'Credits: ' + data.balance.toFixed(2);
|
|
301
470
|
}
|
|
302
471
|
})
|
|
@@ -342,4 +511,45 @@
|
|
|
342
511
|
div.appendChild(document.createTextNode(str));
|
|
343
512
|
return div.innerHTML;
|
|
344
513
|
}
|
|
514
|
+
|
|
515
|
+
function checkPluginReachable(url, callback) {
|
|
516
|
+
var done = false;
|
|
517
|
+
|
|
518
|
+
// Try fetching through the harness server to avoid CORS issues
|
|
519
|
+
fetch('/api/check-plugin')
|
|
520
|
+
.then(function (r) { return r.json(); })
|
|
521
|
+
.then(function (data) {
|
|
522
|
+
if (!done) {
|
|
523
|
+
done = true;
|
|
524
|
+
callback(data.reachable);
|
|
525
|
+
}
|
|
526
|
+
})
|
|
527
|
+
.catch(function () {
|
|
528
|
+
if (!done) {
|
|
529
|
+
done = true;
|
|
530
|
+
// If the check endpoint doesn't exist, assume reachable and let iframe handle it
|
|
531
|
+
callback(true);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
// Timeout after 5 seconds
|
|
536
|
+
setTimeout(function () {
|
|
537
|
+
if (!done) {
|
|
538
|
+
done = true;
|
|
539
|
+
callback(false);
|
|
540
|
+
}
|
|
541
|
+
}, 5000);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function showPluginError(url) {
|
|
545
|
+
pluginStatus.classList.add('error');
|
|
546
|
+
pluginStatus.innerHTML =
|
|
547
|
+
'<p><strong>Plugin server not reachable</strong></p>' +
|
|
548
|
+
'<p>The harness cannot connect to your plugin at<br/><code>' + escapeHtml(url) + '</code></p>' +
|
|
549
|
+
'<p>Make sure your plugin dev server is running:</p>' +
|
|
550
|
+
'<p><code>npm run dev</code></p>' +
|
|
551
|
+
'<p style="color:#6b7280;font-size:12px;margin-top:8px;">' +
|
|
552
|
+
'The harness will automatically retry when you click the reload button (↻).</p>';
|
|
553
|
+
iframe.classList.add('hidden');
|
|
554
|
+
}
|
|
345
555
|
})();
|