@atezer/figma-mcp-bridge 1.3.2 → 1.4.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/CHANGELOG.md +36 -0
- package/README.md +4 -3
- package/dist/core/plugin-bridge-server.d.ts +16 -1
- package/dist/core/plugin-bridge-server.d.ts.map +1 -1
- package/dist/core/plugin-bridge-server.js +62 -2
- package/dist/core/plugin-bridge-server.js.map +1 -1
- package/dist/core/response-guard.d.ts +37 -0
- package/dist/core/response-guard.d.ts.map +1 -0
- package/dist/core/response-guard.js +166 -0
- package/dist/core/response-guard.js.map +1 -0
- package/dist/local-plugin-only.d.ts.map +1 -1
- package/dist/local-plugin-only.js +247 -1
- package/dist/local-plugin-only.js.map +1 -1
- package/dist/local.js +1 -1
- package/f-mcp-plugin/code.js +43 -0
- package/f-mcp-plugin/ui.html +186 -0
- package/package.json +1 -1
package/f-mcp-plugin/ui.html
CHANGED
|
@@ -218,6 +218,31 @@
|
|
|
218
218
|
<input type="number" id="mcp-port" min="5454" max="5470" value="5454" aria-label="MCP bridge port (auto fallback 5454-5470)" title="Elle değiştirirseniz tek porta kilitlenir; Otomatik tara veya Advanced'ı kapatın." />
|
|
219
219
|
</div>
|
|
220
220
|
<button type="button" id="auto-port-reset" class="auto-port-reset" title="Tek porta kilitlenmeyi kaldırır; 5454–5470 yeniden taranır (Claude config portu ile eşleşmeli)." aria-label="5454 ile 5470 arası otomatik port taraması">Otomatik tara</button>
|
|
221
|
+
<div class="api-token-section" style="margin-top:8px;border-top:1px solid rgba(255,255,255,0.08);padding-top:8px;">
|
|
222
|
+
<div style="display:flex;align-items:center;gap:6px;">
|
|
223
|
+
<label for="figma-token" style="font-size:11px;color:rgba(255,255,255,0.5);white-space:nowrap;">API Token</label>
|
|
224
|
+
<input type="password" id="figma-token" placeholder="figd_..." style="flex:1;font-size:11px;padding:3px 6px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);border-radius:4px;color:#fff;outline:none;" aria-label="Figma REST API token" />
|
|
225
|
+
<select id="token-expiry" style="font-size:10px;padding:2px 3px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);border-radius:3px;color:rgba(255,255,255,0.6);outline:none;" title="Token süresi (Figma'da seçtiğiniz süre)">
|
|
226
|
+
<option value="90">90g</option>
|
|
227
|
+
<option value="30">30g</option>
|
|
228
|
+
<option value="7">7g</option>
|
|
229
|
+
<option value="1">1g</option>
|
|
230
|
+
</select>
|
|
231
|
+
<button type="button" id="clear-token" style="font-size:10px;padding:2px 6px;background:rgba(255,100,100,0.15);border:1px solid rgba(255,100,100,0.2);border-radius:3px;color:rgba(255,100,100,0.8);cursor:pointer;" title="Token'ı temizle">x</button>
|
|
232
|
+
</div>
|
|
233
|
+
<div id="token-status" style="font-size:10px;color:rgba(255,255,255,0.35);margin-top:3px;display:none;"></div>
|
|
234
|
+
<div id="token-expiry-info" style="font-size:9px;color:rgba(255,255,255,0.3);margin-top:2px;display:none;"></div>
|
|
235
|
+
<div id="rate-limit-bar" style="display:none;margin-top:4px;">
|
|
236
|
+
<div style="display:flex;justify-content:space-between;font-size:9px;color:rgba(255,255,255,0.4);">
|
|
237
|
+
<span>API Limit</span>
|
|
238
|
+
<span id="rate-limit-text">—</span>
|
|
239
|
+
</div>
|
|
240
|
+
<div style="height:3px;background:rgba(255,255,255,0.08);border-radius:2px;overflow:hidden;margin-top:2px;">
|
|
241
|
+
<div id="rate-limit-fill" style="height:100%;background:#4ecdc4;border-radius:2px;transition:width 0.3s;width:100%;"></div>
|
|
242
|
+
</div>
|
|
243
|
+
<div id="rate-limit-warning" style="display:none;font-size:9px;margin-top:2px;font-weight:600;"></div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
221
246
|
</div>
|
|
222
247
|
</div>
|
|
223
248
|
|
|
@@ -597,6 +622,35 @@
|
|
|
597
622
|
console.log('[F-MCP ATezer Bridge] File identity: ' + (msg.fileName || '?') + ' (' + (msg.fileKey || '?') + ')');
|
|
598
623
|
break;
|
|
599
624
|
|
|
625
|
+
case 'RESTORE_TOKEN':
|
|
626
|
+
// Token restored from figma.clientStorage on plugin open
|
|
627
|
+
if (msg.token) {
|
|
628
|
+
var tokenEl = document.getElementById('figma-token');
|
|
629
|
+
if (tokenEl) tokenEl.value = msg.token;
|
|
630
|
+
var expiresAt = msg.expiresAt || 0;
|
|
631
|
+
__tokenExpiresAt = expiresAt;
|
|
632
|
+
var isExpired = expiresAt > 0 && expiresAt < Date.now();
|
|
633
|
+
if (isExpired) {
|
|
634
|
+
updateTokenUI(false, null, expiresAt);
|
|
635
|
+
// Auto-delete expired token
|
|
636
|
+
parent.postMessage({ pluginMessage: { type: 'DELETE_TOKEN' } }, '*');
|
|
637
|
+
if (tokenEl) tokenEl.value = '';
|
|
638
|
+
} else {
|
|
639
|
+
updateTokenUI(true, null, expiresAt);
|
|
640
|
+
window.__pendingRestoreToken = msg.token;
|
|
641
|
+
}
|
|
642
|
+
console.log('[F-MCP] Token restored from clientStorage' + (isExpired ? ' (EXPIRED)' : '') + (expiresAt ? ' expires: ' + new Date(expiresAt).toLocaleDateString() : ''));
|
|
643
|
+
}
|
|
644
|
+
break;
|
|
645
|
+
|
|
646
|
+
case 'TOKEN_SAVED':
|
|
647
|
+
console.log('[F-MCP] Token saved to clientStorage: ' + (msg.success ? 'OK' : 'FAIL'));
|
|
648
|
+
break;
|
|
649
|
+
|
|
650
|
+
case 'TOKEN_DELETED':
|
|
651
|
+
console.log('[F-MCP] Token deleted from clientStorage');
|
|
652
|
+
break;
|
|
653
|
+
|
|
600
654
|
case 'VARIABLES_DATA':
|
|
601
655
|
window.__figmaVariablesData = msg.data;
|
|
602
656
|
window.__figmaVariablesReady = true;
|
|
@@ -815,6 +869,7 @@
|
|
|
815
869
|
var MCP_BRIDGE_PORT_KEY = 'f-mcp-bridge-port';
|
|
816
870
|
var MCP_BRIDGE_HOST_KEY = 'f-mcp-bridge-host';
|
|
817
871
|
var MCP_ADVANCED_OPEN_KEY = 'f-mcp-bridge-advanced-open';
|
|
872
|
+
|
|
818
873
|
var mcpBridgeWs = null;
|
|
819
874
|
var mcpBridgeReconnectTimer = null;
|
|
820
875
|
var mcpReconnectDelay = 1000;
|
|
@@ -858,6 +913,122 @@
|
|
|
858
913
|
requestUiResize();
|
|
859
914
|
}
|
|
860
915
|
|
|
916
|
+
// ---- Token UI ----
|
|
917
|
+
function updateTokenUI(hasToken, rateLimit, expiresAt) {
|
|
918
|
+
var statusEl = document.getElementById('token-status');
|
|
919
|
+
var barEl = document.getElementById('rate-limit-bar');
|
|
920
|
+
var textEl = document.getElementById('rate-limit-text');
|
|
921
|
+
var fillEl = document.getElementById('rate-limit-fill');
|
|
922
|
+
var expiryEl = document.getElementById('token-expiry-info');
|
|
923
|
+
if (statusEl) {
|
|
924
|
+
statusEl.style.display = 'block';
|
|
925
|
+
statusEl.textContent = hasToken ? 'Token aktif' : 'Token yok';
|
|
926
|
+
statusEl.style.color = hasToken ? 'rgba(78,205,196,0.8)' : 'rgba(255,255,255,0.35)';
|
|
927
|
+
}
|
|
928
|
+
// Expiry countdown
|
|
929
|
+
if (expiryEl) {
|
|
930
|
+
if (hasToken && expiresAt && expiresAt > 0) {
|
|
931
|
+
var now = Date.now();
|
|
932
|
+
var daysLeft = Math.ceil((expiresAt - now) / (24 * 60 * 60 * 1000));
|
|
933
|
+
if (daysLeft <= 0) {
|
|
934
|
+
expiryEl.style.display = 'block';
|
|
935
|
+
expiryEl.textContent = 'Token suresi dolmus!';
|
|
936
|
+
expiryEl.style.color = 'rgba(249,65,68,0.9)';
|
|
937
|
+
if (statusEl) { statusEl.textContent = 'Token suresi dolmus'; statusEl.style.color = 'rgba(249,65,68,0.8)'; }
|
|
938
|
+
} else {
|
|
939
|
+
expiryEl.style.display = 'block';
|
|
940
|
+
var expDate = new Date(expiresAt);
|
|
941
|
+
expiryEl.textContent = daysLeft + ' gun kaldi (' + expDate.toLocaleDateString('tr-TR') + ')';
|
|
942
|
+
expiryEl.style.color = daysLeft <= 7 ? 'rgba(249,193,79,0.8)' : 'rgba(255,255,255,0.35)';
|
|
943
|
+
}
|
|
944
|
+
} else {
|
|
945
|
+
expiryEl.style.display = 'none';
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
// Rate limit bar
|
|
949
|
+
if (rateLimit && rateLimit.limit > 0 && barEl && textEl && fillEl) {
|
|
950
|
+
barEl.style.display = 'block';
|
|
951
|
+
var pct = Math.round((rateLimit.remaining / rateLimit.limit) * 100);
|
|
952
|
+
textEl.textContent = rateLimit.remaining + ' / ' + rateLimit.limit;
|
|
953
|
+
fillEl.style.width = pct + '%';
|
|
954
|
+
fillEl.style.background = pct > 30 ? '#4ecdc4' : pct > 10 ? '#f9c74f' : '#f94144';
|
|
955
|
+
// Warning text
|
|
956
|
+
var warnEl = document.getElementById('rate-limit-warning');
|
|
957
|
+
if (warnEl) {
|
|
958
|
+
if (rateLimit.remaining === 0) {
|
|
959
|
+
warnEl.style.display = 'block';
|
|
960
|
+
warnEl.textContent = 'API limiti doldu!';
|
|
961
|
+
warnEl.style.color = '#f94144';
|
|
962
|
+
} else if (pct <= 5) {
|
|
963
|
+
warnEl.style.display = 'block';
|
|
964
|
+
warnEl.textContent = 'API limiti kritik!';
|
|
965
|
+
warnEl.style.color = '#f94144';
|
|
966
|
+
fillEl.style.animation = 'pulse 1s infinite';
|
|
967
|
+
} else if (pct <= 20) {
|
|
968
|
+
warnEl.style.display = 'block';
|
|
969
|
+
warnEl.textContent = 'Dusuk API limiti';
|
|
970
|
+
warnEl.style.color = '#f9c74f';
|
|
971
|
+
fillEl.style.animation = 'none';
|
|
972
|
+
} else {
|
|
973
|
+
warnEl.style.display = 'none';
|
|
974
|
+
fillEl.style.animation = 'none';
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
} else if (barEl) {
|
|
978
|
+
barEl.style.display = hasToken ? 'block' : 'none';
|
|
979
|
+
if (textEl) textEl.textContent = '—';
|
|
980
|
+
if (fillEl) { fillEl.style.width = '100%'; fillEl.style.animation = 'none'; }
|
|
981
|
+
var warnEl2 = document.getElementById('rate-limit-warning');
|
|
982
|
+
if (warnEl2) warnEl2.style.display = 'none';
|
|
983
|
+
}
|
|
984
|
+
requestUiResize();
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Track current token expiry for UI updates
|
|
988
|
+
var __tokenExpiresAt = 0;
|
|
989
|
+
|
|
990
|
+
function getSelectedExpiryDays() {
|
|
991
|
+
var sel = document.getElementById('token-expiry');
|
|
992
|
+
return sel ? parseInt(sel.value, 10) || 90 : 90;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
function initTokenInput() {
|
|
996
|
+
var el = document.getElementById('figma-token');
|
|
997
|
+
var clearBtn = document.getElementById('clear-token');
|
|
998
|
+
if (el) {
|
|
999
|
+
el.addEventListener('change', function() {
|
|
1000
|
+
var token = (el.value || '').trim();
|
|
1001
|
+
if (token) {
|
|
1002
|
+
var days = getSelectedExpiryDays();
|
|
1003
|
+
var expiresAt = Date.now() + days * 24 * 60 * 60 * 1000;
|
|
1004
|
+
__tokenExpiresAt = expiresAt;
|
|
1005
|
+
// Save to Figma clientStorage (persists across plugin close/reopen)
|
|
1006
|
+
parent.postMessage({ pluginMessage: { type: 'SAVE_TOKEN', token: token, expiresAt: expiresAt } }, '*');
|
|
1007
|
+
// Send to bridge
|
|
1008
|
+
if (mcpBridgeWs && mcpBridgeWs.readyState === 1) {
|
|
1009
|
+
mcpBridgeWs.send(JSON.stringify({ type: 'setToken', token: token }));
|
|
1010
|
+
}
|
|
1011
|
+
updateTokenUI(true, null, expiresAt);
|
|
1012
|
+
} else {
|
|
1013
|
+
updateTokenUI(false, null, 0);
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
if (clearBtn) {
|
|
1018
|
+
clearBtn.addEventListener('click', function() {
|
|
1019
|
+
if (el) el.value = '';
|
|
1020
|
+
__tokenExpiresAt = 0;
|
|
1021
|
+
// Delete from Figma clientStorage
|
|
1022
|
+
parent.postMessage({ pluginMessage: { type: 'DELETE_TOKEN' } }, '*');
|
|
1023
|
+
// Clear from bridge
|
|
1024
|
+
if (mcpBridgeWs && mcpBridgeWs.readyState === 1) {
|
|
1025
|
+
mcpBridgeWs.send(JSON.stringify({ type: 'clearToken' }));
|
|
1026
|
+
}
|
|
1027
|
+
updateTokenUI(false, null, 0);
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
861
1032
|
function initAdvancedPanel() {
|
|
862
1033
|
var toggle = document.getElementById('advanced-toggle');
|
|
863
1034
|
var initial = false;
|
|
@@ -1057,6 +1228,20 @@
|
|
|
1057
1228
|
}
|
|
1058
1229
|
console.log('[F-MCP ATezer Bridge] Handshake OK — bridge v' + (msg.bridgeVersion || '?') + ' on port ' + mcpConnectedPort);
|
|
1059
1230
|
updateStatus('ready (:' + mcpConnectedPort + ')', true, false);
|
|
1231
|
+
// Resend restored token to bridge after handshake
|
|
1232
|
+
if (window.__pendingRestoreToken && currentWs && currentWs.readyState === 1) {
|
|
1233
|
+
currentWs.send(JSON.stringify({ type: 'setToken', token: window.__pendingRestoreToken }));
|
|
1234
|
+
console.log('[F-MCP] Sent restored token to bridge');
|
|
1235
|
+
}
|
|
1236
|
+
// Also check input field (user may have entered token before connection)
|
|
1237
|
+
var tokenInput = document.getElementById('figma-token');
|
|
1238
|
+
if (!window.__pendingRestoreToken && tokenInput && tokenInput.value && currentWs && currentWs.readyState === 1) {
|
|
1239
|
+
currentWs.send(JSON.stringify({ type: 'setToken', token: tokenInput.value.trim() }));
|
|
1240
|
+
}
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (msg.type === 'tokenStatus') {
|
|
1244
|
+
updateTokenUI(msg.hasToken, msg.rateLimit || null, __tokenExpiresAt);
|
|
1060
1245
|
return;
|
|
1061
1246
|
}
|
|
1062
1247
|
if (msg.type === 'ping') {
|
|
@@ -1268,6 +1453,7 @@
|
|
|
1268
1453
|
if (typeof WebSocket !== 'undefined') {
|
|
1269
1454
|
initMcpHostInput();
|
|
1270
1455
|
initMcpPortInput();
|
|
1456
|
+
initTokenInput();
|
|
1271
1457
|
initAdvancedPanel();
|
|
1272
1458
|
try {
|
|
1273
1459
|
var rememberedPort = parseInt(localStorage.getItem(MCP_BRIDGE_PORT_KEY) || '', 10);
|
package/package.json
CHANGED