@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atezer/figma-mcp-bridge",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "F-MCP ATezer: MCP server and Figma plugin bridge for Claude/Cursor. No REST token required.",
5
5
  "type": "module",
6
6
  "main": "dist/local.js",