@symbo.ls/connect 3.2.7

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.
Files changed (77) hide show
  1. package/build.js +205 -0
  2. package/dist/assets/1024x1024.png +0 -0
  3. package/dist/assets/128x128.png +0 -0
  4. package/dist/assets/144x144.png +0 -0
  5. package/dist/assets/192x192.png +0 -0
  6. package/dist/assets/48x48.png +0 -0
  7. package/dist/assets/512x512.png +0 -0
  8. package/dist/assets/72x72.png +0 -0
  9. package/dist/assets/96x96.png +0 -0
  10. package/dist/assets/active_cursor.png +0 -0
  11. package/dist/assets/favicon.svg +6 -0
  12. package/dist/assets/old/144x144.png +0 -0
  13. package/dist/assets/old/192x192.png +0 -0
  14. package/dist/assets/old/48x48.png +0 -0
  15. package/dist/assets/old/48x48_faint.png +0 -0
  16. package/dist/assets/old/512x512.png +0 -0
  17. package/dist/assets/old/72x72.png +0 -0
  18. package/dist/assets/old/96x96.png +0 -0
  19. package/dist/auth.js +373 -0
  20. package/dist/content.css +46 -0
  21. package/dist/content.js +1171 -0
  22. package/dist/content.js.map +7 -0
  23. package/dist/devtools.html +7 -0
  24. package/dist/devtools.js +5 -0
  25. package/dist/manifest.json +87 -0
  26. package/dist/page-agent.js +727 -0
  27. package/dist/panel.css +2239 -0
  28. package/dist/panel.html +235 -0
  29. package/dist/panel.js +4973 -0
  30. package/dist/picker.html +111 -0
  31. package/dist/picker.js +300 -0
  32. package/dist/service_worker.js +219 -0
  33. package/dist/service_worker.js.map +7 -0
  34. package/dist/settings.css +128 -0
  35. package/dist/settings.html +26 -0
  36. package/dist/settings_ui.js +57 -0
  37. package/dist/settings_ui.js.map +7 -0
  38. package/package.json +20 -0
  39. package/src/content.js +104 -0
  40. package/src/grabber/clean.js +605 -0
  41. package/src/grabber/computed.js +78 -0
  42. package/src/grabber/parse.js +268 -0
  43. package/src/grabber/stylesheets.js +117 -0
  44. package/src/grabber/utils.js +238 -0
  45. package/src/service_worker.js +246 -0
  46. package/src/settings/settings_ui.js +52 -0
  47. package/src/settings/settings_utils.js +70 -0
  48. package/static/assets/1024x1024.png +0 -0
  49. package/static/assets/128x128.png +0 -0
  50. package/static/assets/144x144.png +0 -0
  51. package/static/assets/192x192.png +0 -0
  52. package/static/assets/48x48.png +0 -0
  53. package/static/assets/512x512.png +0 -0
  54. package/static/assets/72x72.png +0 -0
  55. package/static/assets/96x96.png +0 -0
  56. package/static/assets/active_cursor.png +0 -0
  57. package/static/assets/favicon.svg +6 -0
  58. package/static/assets/old/144x144.png +0 -0
  59. package/static/assets/old/192x192.png +0 -0
  60. package/static/assets/old/48x48.png +0 -0
  61. package/static/assets/old/48x48_faint.png +0 -0
  62. package/static/assets/old/512x512.png +0 -0
  63. package/static/assets/old/72x72.png +0 -0
  64. package/static/assets/old/96x96.png +0 -0
  65. package/static/auth.js +373 -0
  66. package/static/content.css +46 -0
  67. package/static/devtools.html +7 -0
  68. package/static/devtools.js +5 -0
  69. package/static/manifest.json +56 -0
  70. package/static/page-agent.js +727 -0
  71. package/static/panel.css +2239 -0
  72. package/static/panel.html +235 -0
  73. package/static/panel.js +4973 -0
  74. package/static/picker.html +111 -0
  75. package/static/picker.js +300 -0
  76. package/static/settings.css +128 -0
  77. package/static/settings.html +26 -0
package/static/auth.js ADDED
@@ -0,0 +1,373 @@
1
+ // Auth module — manages login/logout and token storage via chrome.storage.local
2
+ ;(function () {
3
+ 'use strict'
4
+
5
+ const API_BASE = 'https://api.symbols.app'
6
+ const STORAGE_KEYS = {
7
+ accessToken: 'symbols_access_token',
8
+ refreshToken: 'symbols_refresh_token',
9
+ expiresAt: 'symbols_expires_at',
10
+ user: 'symbols_user'
11
+ }
12
+
13
+ // ============================================================
14
+ // Token storage (chrome.storage.local)
15
+ // ============================================================
16
+ function getStoredAuth () {
17
+ return new Promise((resolve) => {
18
+ chrome.storage.local.get(Object.values(STORAGE_KEYS), (data) => {
19
+ resolve({
20
+ accessToken: data[STORAGE_KEYS.accessToken] || null,
21
+ refreshToken: data[STORAGE_KEYS.refreshToken] || null,
22
+ expiresAt: data[STORAGE_KEYS.expiresAt] || 0,
23
+ user: data[STORAGE_KEYS.user] || null
24
+ })
25
+ })
26
+ })
27
+ }
28
+
29
+ function setStoredAuth (tokens, user) {
30
+ const data = {}
31
+ if (tokens) {
32
+ data[STORAGE_KEYS.accessToken] = tokens.accessToken
33
+ data[STORAGE_KEYS.refreshToken] = tokens.refreshToken
34
+ data[STORAGE_KEYS.expiresAt] = tokens.accessTokenExp
35
+ ? (tokens.accessTokenExp.expiresAt || (Date.now() / 1000 + (tokens.accessTokenExp.expiresIn || 3600)))
36
+ : (Date.now() / 1000 + 3600)
37
+ }
38
+ if (user) {
39
+ data[STORAGE_KEYS.user] = user
40
+ }
41
+ return new Promise((resolve) => chrome.storage.local.set(data, resolve))
42
+ }
43
+
44
+ function clearStoredAuth () {
45
+ return new Promise((resolve) => {
46
+ chrome.storage.local.remove(Object.values(STORAGE_KEYS), resolve)
47
+ })
48
+ }
49
+
50
+ // ============================================================
51
+ // API calls (routed through service worker to bypass CORS)
52
+ // ============================================================
53
+ async function apiRequest (path, options) {
54
+ var url = API_BASE + path
55
+ var headers = { 'Content-Type': 'application/json' }
56
+ if (options && options.headers) {
57
+ Object.assign(headers, options.headers)
58
+ }
59
+ var resp = await swFetch(url, {
60
+ method: (options && options.method) || 'GET',
61
+ headers: headers,
62
+ body: (options && options.body) || undefined
63
+ })
64
+ if (!resp.ok) {
65
+ throw new Error((resp.data && resp.data.message) || resp.text || resp.error || ('HTTP ' + resp.status))
66
+ }
67
+ return resp.data
68
+ }
69
+
70
+ async function authedRequest (path, options) {
71
+ const auth = await getStoredAuth()
72
+ let token = auth.accessToken
73
+
74
+ // Refresh if expired (30s buffer)
75
+ if (token && auth.expiresAt && (Date.now() / 1000) > auth.expiresAt - 30) {
76
+ try {
77
+ const refreshed = await refreshTokens(auth.refreshToken)
78
+ token = refreshed.accessToken
79
+ } catch (e) {
80
+ // Refresh failed — clear auth
81
+ await clearStoredAuth()
82
+ throw new Error('Session expired — please sign in again')
83
+ }
84
+ }
85
+
86
+ if (!token) throw new Error('Not signed in')
87
+
88
+ return apiRequest(path, {
89
+ ...options,
90
+ headers: {
91
+ ...(options && options.headers),
92
+ Authorization: 'Bearer ' + token
93
+ }
94
+ })
95
+ }
96
+
97
+ // ============================================================
98
+ // Auth operations
99
+ // ============================================================
100
+ async function login (email, password) {
101
+ const data = await apiRequest('/core/auth/login', {
102
+ method: 'POST',
103
+ body: JSON.stringify({ email, password })
104
+ })
105
+
106
+ const tokens = data.tokens || data
107
+ const user = data.user || null
108
+ await setStoredAuth(tokens, user)
109
+ return { tokens, user }
110
+ }
111
+
112
+ async function refreshTokens (refreshToken) {
113
+ const data = await apiRequest('/core/auth/refresh', {
114
+ method: 'POST',
115
+ body: JSON.stringify({ refreshToken })
116
+ })
117
+ const tokens = data.tokens || data
118
+ const auth = await getStoredAuth()
119
+ await setStoredAuth(tokens, auth.user)
120
+ return tokens
121
+ }
122
+
123
+ async function getMe () {
124
+ return authedRequest('/core/auth/me', { method: 'GET' })
125
+ }
126
+
127
+ async function logout () {
128
+ try {
129
+ const auth = await getStoredAuth()
130
+ if (auth.accessToken) {
131
+ await apiRequest('/core/auth/logout', {
132
+ method: 'POST',
133
+ headers: { Authorization: 'Bearer ' + auth.accessToken }
134
+ }).catch(() => {})
135
+ }
136
+ } finally {
137
+ await clearStoredAuth()
138
+ }
139
+ }
140
+
141
+ async function isSignedIn () {
142
+ const auth = await getStoredAuth()
143
+ if (!auth.accessToken) return false
144
+ try {
145
+ await getMe()
146
+ return true
147
+ } catch (e) {
148
+ return false
149
+ }
150
+ }
151
+
152
+ // ============================================================
153
+ // Platform API — projects
154
+ // ============================================================
155
+ async function listProjects () {
156
+ return authedRequest('/core/projects', { method: 'GET' })
157
+ }
158
+
159
+ async function getProjectByKey (key) {
160
+ return authedRequest('/core/projects/key/' + encodeURIComponent(key), { method: 'GET' })
161
+ }
162
+
163
+ async function getProjectData (projectId, branch) {
164
+ return authedRequest(
165
+ '/core/projects/' + projectId + '/data?branch=' + (branch || 'main') + '&version=latest',
166
+ { method: 'GET' }
167
+ )
168
+ }
169
+
170
+ async function getServiceToken () {
171
+ try {
172
+ const res = await fetch(API_BASE + '/service-token')
173
+ const json = await res.json().catch(() => null)
174
+ if (json && json.token) return json.token.trim()
175
+ const txt = await res.text()
176
+ return (txt || '').replace(/\s+/g, '') || null
177
+ } catch (e) {
178
+ return null
179
+ }
180
+ }
181
+
182
+ async function getAccessToken () {
183
+ const auth = await getStoredAuth()
184
+ return auth.accessToken
185
+ }
186
+
187
+ // ============================================================
188
+ // Browser sign-in (PKCE session flow — same as CLI)
189
+ // ============================================================
190
+ const WEBSITE_BASE = 'https://symbols.app'
191
+
192
+ function randomVerifier (len) {
193
+ len = len || 64
194
+ var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~'
195
+ var arr = new Uint8Array(len)
196
+ crypto.getRandomValues(arr)
197
+ var out = ''
198
+ for (var i = 0; i < len; i++) out += chars.charAt(arr[i] % chars.length)
199
+ return out
200
+ }
201
+
202
+ async function sha256Base64url (input) {
203
+ var encoder = new TextEncoder()
204
+ var data = encoder.encode(input)
205
+ var hash = await crypto.subtle.digest('SHA-256', data)
206
+ var bytes = new Uint8Array(hash)
207
+ var binary = ''
208
+ for (var i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i])
209
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '')
210
+ }
211
+
212
+ // Proxy fetch through service worker to avoid CORS preflight issues
213
+ function swFetch (url, options) {
214
+ return new Promise(function (resolve, reject) {
215
+ try {
216
+ chrome.runtime.sendMessage({
217
+ type: 'api-fetch',
218
+ url: url,
219
+ method: (options && options.method) || 'GET',
220
+ headers: (options && options.headers) || {},
221
+ body: (options && options.body) || undefined
222
+ }, function (resp) {
223
+ if (chrome.runtime.lastError) {
224
+ reject(new Error(chrome.runtime.lastError.message))
225
+ return
226
+ }
227
+ if (!resp) {
228
+ reject(new Error('No response from service worker'))
229
+ return
230
+ }
231
+ resolve(resp)
232
+ })
233
+ } catch (e) {
234
+ reject(new Error('sendMessage failed: ' + (e.message || e)))
235
+ }
236
+ })
237
+ }
238
+
239
+ // Try swFetch — no fallback to direct fetch (CORS blocks it)
240
+ async function proxyFetch (url, options) {
241
+ return swFetch(url, options)
242
+ }
243
+
244
+ async function loginViaBrowser (onStatus) {
245
+ var sessionId = crypto.randomUUID()
246
+ var codeVerifier = randomVerifier(64)
247
+ var codeChallenge = await sha256Base64url(codeVerifier)
248
+
249
+ if (onStatus) onStatus('Creating secure session...')
250
+
251
+ // Create PKCE session via service worker (avoids CORS)
252
+ var sessionResp
253
+ try {
254
+ sessionResp = await proxyFetch(API_BASE + '/core/auth/session', {
255
+ method: 'POST',
256
+ headers: { 'Content-Type': 'application/json' },
257
+ body: JSON.stringify({
258
+ session_id: sessionId,
259
+ code_challenge: codeChallenge,
260
+ plugin_info: { version: 'chrome-extension', figma_env: 'chrome' }
261
+ })
262
+ })
263
+ } catch (fetchErr) {
264
+ chrome.tabs.create({ url: WEBSITE_BASE + '/signin' })
265
+ throw new Error('Session fetch error: ' + fetchErr.message)
266
+ }
267
+
268
+ if (!sessionResp.ok) {
269
+ chrome.tabs.create({ url: WEBSITE_BASE + '/signin' })
270
+ throw new Error('Session failed (HTTP ' + sessionResp.status + '): ' + (sessionResp.text || '').substring(0, 120))
271
+ }
272
+
273
+ // Open signin with session param
274
+ var signinUrl = WEBSITE_BASE + '/signin?session=' + encodeURIComponent(sessionId)
275
+ chrome.tabs.create({ url: signinUrl })
276
+
277
+ if (onStatus) onStatus('Waiting for sign-in in browser...')
278
+
279
+ var startedAt = Date.now()
280
+ var timeoutMs = 3 * 60 * 1000
281
+ var pollMs = 1500
282
+
283
+ while (true) {
284
+ if (Date.now() - startedAt > timeoutMs) {
285
+ throw new Error('Sign-in timed out — please try again')
286
+ }
287
+
288
+ await new Promise(function (r) { setTimeout(r, pollMs) })
289
+
290
+ try {
291
+ var statusResp = await swFetch(
292
+ API_BASE + '/core/auth/session/status?session=' + encodeURIComponent(sessionId),
293
+ { method: 'GET' }
294
+ )
295
+
296
+ if (!statusResp.ok) continue
297
+
298
+ var statusData = statusResp.data || {}
299
+ var status = statusData.status || statusData.data?.status || statusData.state || statusData.data?.state
300
+ if (status === 'ready_for_confirm') break
301
+ if (status === 'expired' || status === 'revoked' || status === 'invalid') {
302
+ throw new Error('Session ' + status + ' — please try again')
303
+ }
304
+ } catch (pollErr) {
305
+ if (pollErr.message && pollErr.message.includes('Session')) throw pollErr
306
+ }
307
+ }
308
+
309
+ if (onStatus) onStatus('Confirming session...')
310
+
311
+ var confirmResp = await proxyFetch(API_BASE + '/core/auth/session/confirm', {
312
+ method: 'POST',
313
+ headers: { 'Content-Type': 'application/json' },
314
+ body: JSON.stringify({
315
+ session_id: sessionId,
316
+ code_verifier: codeVerifier
317
+ })
318
+ })
319
+
320
+ if (!confirmResp.ok) {
321
+ var confirmData = confirmResp.data || {}
322
+ throw new Error(confirmData.message || confirmData.error || 'Session confirm failed')
323
+ }
324
+
325
+ var cData = confirmResp.data || {}
326
+ var token = cData.access_token || cData.data?.access_token ||
327
+ cData.token || cData.data?.token
328
+ if (!token) {
329
+ throw new Error('Sign-in succeeded but no token returned')
330
+ }
331
+
332
+ var tempTokens = { accessToken: token, refreshToken: null, accessTokenExp: null }
333
+ await setStoredAuth(tempTokens, null)
334
+
335
+ try {
336
+ var user = await getMe()
337
+ await setStoredAuth(tempTokens, user)
338
+ return { tokens: tempTokens, user: user }
339
+ } catch (e) {
340
+ return { tokens: tempTokens, user: null }
341
+ }
342
+ }
343
+
344
+ // ============================================================
345
+ // AI — platform AI endpoint
346
+ // ============================================================
347
+ async function aiPrompt (messages, context) {
348
+ return authedRequest('/core/ai/chat', {
349
+ method: 'POST',
350
+ body: JSON.stringify({ messages, context })
351
+ })
352
+ }
353
+
354
+ // ============================================================
355
+ // Expose global API
356
+ // ============================================================
357
+ window.SymbolsAuth = {
358
+ login,
359
+ loginViaBrowser,
360
+ logout,
361
+ getMe,
362
+ isSignedIn,
363
+ getStoredAuth,
364
+ getAccessToken,
365
+ getServiceToken,
366
+ listProjects,
367
+ getProjectByKey,
368
+ getProjectData,
369
+ authedRequest,
370
+ aiPrompt,
371
+ API_BASE
372
+ }
373
+ })()
@@ -0,0 +1,46 @@
1
+ #symbols-overlay {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100vw;
6
+ height: 100vh;
7
+ z-index: 999999999998;
8
+ opacity: 0;
9
+ transition: opacity 350ms cubic-bezier(0.29, 0.67, 0.51, 0.97);
10
+ }
11
+
12
+ body:not(.symbols-grabber-active) #symbols-overlay {
13
+ pointer-events: none;
14
+ }
15
+
16
+ :not(#symbols-overlay) .symbols-grabber-hovered {
17
+ outline: 3px solid #0085ff !important;
18
+ outline-offset: 1px !important;
19
+ }
20
+
21
+ iframe#symbols-frame {
22
+ position: fixed;
23
+ top: 50%;
24
+ left: 50%;
25
+ width: 85vw;
26
+ height: 85vh;
27
+ border-radius: 0.618em;
28
+ transform: translate(-50%, -50%);
29
+ border: none;
30
+ box-shadow: #121212f7 0 0 0 1000em;
31
+ background: #121212f7;
32
+ }
33
+
34
+ div#close-btn {
35
+ cursor: pointer;
36
+ z-index: 9999999999999;
37
+ position: fixed;
38
+ top: 5vh;
39
+ right: 6.4vw;
40
+ font-size: 1.5rem;
41
+ line-height: 0.9;
42
+ width: 1em;
43
+ height: 1em;
44
+ color: white;
45
+ font-family: monospace;
46
+ }
@@ -0,0 +1,7 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><meta charset="UTF-8"></head>
4
+ <body>
5
+ <script src="devtools.js"></script>
6
+ </body>
7
+ </html>
@@ -0,0 +1,5 @@
1
+ chrome.devtools.panels.create('Symbols', null, 'panel.html', panel => {
2
+ panel.onShown.addListener(win => {
3
+ win.panelShown()
4
+ })
5
+ })
@@ -0,0 +1,56 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "Symbols Connect",
4
+ "version": "3.0.0",
5
+ "description": "symbols.app connect — DOMQL Inspector and element grabber for the Symbols design system",
6
+ "permissions": ["scripting", "tabs", "activeTab", "storage", "declarativeNetRequest"],
7
+ "host_permissions": ["<all_urls>"],
8
+ "icons": {
9
+ "48": "assets/48x48.png",
10
+ "144": "assets/144x144.png"
11
+ },
12
+ "action": {
13
+ "default_icon": {
14
+ "48": "assets/48x48.png",
15
+ "72": "assets/72x72.png",
16
+ "144": "assets/144x144.png"
17
+ },
18
+ "default_title": "Symbols Connect"
19
+ },
20
+ "background": {
21
+ "service_worker": "service_worker.js"
22
+ },
23
+ "devtools_page": "devtools.html",
24
+ "options_page": "settings.html",
25
+ "commands": {
26
+ "toggleGrabber": {
27
+ "suggested_key": {
28
+ "default": "Ctrl+E",
29
+ "mac": "Command+E"
30
+ },
31
+ "description": "Toggle grabber mode"
32
+ }
33
+ },
34
+ "content_scripts": [
35
+ {
36
+ "matches": ["https://*/*", "http://*/*"],
37
+ "exclude_globs": ["https://symbols.app/*", "https://platform.symbo.ls/*"],
38
+ "js": ["content.js"],
39
+ "run_at": "document_end",
40
+ "all_frames": true
41
+ },
42
+ {
43
+ "matches": ["https://*/*", "http://*/*"],
44
+ "css": ["content.css"],
45
+ "run_at": "document_end",
46
+ "all_frames": true
47
+ }
48
+ ],
49
+ "web_accessible_resources": [{
50
+ "resources": ["page-agent.js", "picker.html"],
51
+ "matches": ["<all_urls>"]
52
+ }],
53
+ "externally_connectable": {
54
+ "matches": ["https://symbols.app/*", "https://platform.symbo.ls/*"]
55
+ }
56
+ }