@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.
- package/build.js +205 -0
- package/dist/assets/1024x1024.png +0 -0
- package/dist/assets/128x128.png +0 -0
- package/dist/assets/144x144.png +0 -0
- package/dist/assets/192x192.png +0 -0
- package/dist/assets/48x48.png +0 -0
- package/dist/assets/512x512.png +0 -0
- package/dist/assets/72x72.png +0 -0
- package/dist/assets/96x96.png +0 -0
- package/dist/assets/active_cursor.png +0 -0
- package/dist/assets/favicon.svg +6 -0
- package/dist/assets/old/144x144.png +0 -0
- package/dist/assets/old/192x192.png +0 -0
- package/dist/assets/old/48x48.png +0 -0
- package/dist/assets/old/48x48_faint.png +0 -0
- package/dist/assets/old/512x512.png +0 -0
- package/dist/assets/old/72x72.png +0 -0
- package/dist/assets/old/96x96.png +0 -0
- package/dist/auth.js +373 -0
- package/dist/content.css +46 -0
- package/dist/content.js +1171 -0
- package/dist/content.js.map +7 -0
- package/dist/devtools.html +7 -0
- package/dist/devtools.js +5 -0
- package/dist/manifest.json +87 -0
- package/dist/page-agent.js +727 -0
- package/dist/panel.css +2239 -0
- package/dist/panel.html +235 -0
- package/dist/panel.js +4973 -0
- package/dist/picker.html +111 -0
- package/dist/picker.js +300 -0
- package/dist/service_worker.js +219 -0
- package/dist/service_worker.js.map +7 -0
- package/dist/settings.css +128 -0
- package/dist/settings.html +26 -0
- package/dist/settings_ui.js +57 -0
- package/dist/settings_ui.js.map +7 -0
- package/package.json +20 -0
- package/src/content.js +104 -0
- package/src/grabber/clean.js +605 -0
- package/src/grabber/computed.js +78 -0
- package/src/grabber/parse.js +268 -0
- package/src/grabber/stylesheets.js +117 -0
- package/src/grabber/utils.js +238 -0
- package/src/service_worker.js +246 -0
- package/src/settings/settings_ui.js +52 -0
- package/src/settings/settings_utils.js +70 -0
- package/static/assets/1024x1024.png +0 -0
- package/static/assets/128x128.png +0 -0
- package/static/assets/144x144.png +0 -0
- package/static/assets/192x192.png +0 -0
- package/static/assets/48x48.png +0 -0
- package/static/assets/512x512.png +0 -0
- package/static/assets/72x72.png +0 -0
- package/static/assets/96x96.png +0 -0
- package/static/assets/active_cursor.png +0 -0
- package/static/assets/favicon.svg +6 -0
- package/static/assets/old/144x144.png +0 -0
- package/static/assets/old/192x192.png +0 -0
- package/static/assets/old/48x48.png +0 -0
- package/static/assets/old/48x48_faint.png +0 -0
- package/static/assets/old/512x512.png +0 -0
- package/static/assets/old/72x72.png +0 -0
- package/static/assets/old/96x96.png +0 -0
- package/static/auth.js +373 -0
- package/static/content.css +46 -0
- package/static/devtools.html +7 -0
- package/static/devtools.js +5 -0
- package/static/manifest.json +56 -0
- package/static/page-agent.js +727 -0
- package/static/panel.css +2239 -0
- package/static/panel.html +235 -0
- package/static/panel.js +4973 -0
- package/static/picker.html +111 -0
- package/static/picker.js +300 -0
- package/static/settings.css +128 -0
- package/static/settings.html +26 -0
package/build.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// try to refactor typical html css files into symbols in smbls/extensions/chrome-inspect2/static
|
|
4
|
+
const esbuild = require('esbuild')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const fs = require('fs')
|
|
7
|
+
|
|
8
|
+
const root = __dirname
|
|
9
|
+
const srcDir = path.join(root, 'src')
|
|
10
|
+
const staticDir = path.join(root, 'static')
|
|
11
|
+
const outDir = path.join(root, 'dist')
|
|
12
|
+
|
|
13
|
+
const ENTRIES = [
|
|
14
|
+
{
|
|
15
|
+
in: path.join(srcDir, 'content.js'),
|
|
16
|
+
out: path.join(outDir, 'content.js'),
|
|
17
|
+
name: 'content'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
in: path.join(srcDir, 'settings', 'settings_ui.js'),
|
|
21
|
+
out: path.join(outDir, 'settings_ui.js'),
|
|
22
|
+
name: 'settings'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
in: path.join(srcDir, 'service_worker.js'),
|
|
26
|
+
out: path.join(outDir, 'service_worker.js'),
|
|
27
|
+
name: 'service_worker'
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
const copyRecursive = (src, dest) => {
|
|
32
|
+
if (!fs.existsSync(src)) return
|
|
33
|
+
const stats = fs.statSync(src)
|
|
34
|
+
if (stats.isDirectory()) {
|
|
35
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true })
|
|
36
|
+
for (const entry of fs.readdirSync(src)) {
|
|
37
|
+
copyRecursive(path.join(src, entry), path.join(dest, entry))
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
fs.copyFileSync(src, dest)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const copyStatic = () => {
|
|
45
|
+
if (fs.existsSync(staticDir)) {
|
|
46
|
+
for (const entry of fs.readdirSync(staticDir)) {
|
|
47
|
+
const srcEntry = path.join(staticDir, entry)
|
|
48
|
+
const destEntry = path.join(outDir, entry)
|
|
49
|
+
if (fs.existsSync(destEntry)) {
|
|
50
|
+
fs.rmSync(destEntry, { recursive: true, force: true })
|
|
51
|
+
}
|
|
52
|
+
copyRecursive(srcEntry, destEntry)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
console.log('Copied static files.')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const syncManifestVersion = () => {
|
|
59
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
|
|
60
|
+
const manifestPath = path.join(outDir, 'manifest.json')
|
|
61
|
+
if (!fs.existsSync(manifestPath)) return
|
|
62
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
|
|
63
|
+
manifest.version = pkg.version
|
|
64
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8')
|
|
65
|
+
console.log(`Manifest version synced to ${pkg.version}`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const makeEsbuildOptions = ({
|
|
69
|
+
entry,
|
|
70
|
+
outfile,
|
|
71
|
+
minify = false,
|
|
72
|
+
sourcemap = false
|
|
73
|
+
}) => ({
|
|
74
|
+
entryPoints: [entry],
|
|
75
|
+
bundle: true,
|
|
76
|
+
format: 'esm',
|
|
77
|
+
target: ['esnext'],
|
|
78
|
+
outfile,
|
|
79
|
+
define: { 'process.env.NODE_ENV': '"development"' },
|
|
80
|
+
minify,
|
|
81
|
+
sourcemap
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const rewriteUrls = (oldDomain, newDomain) => {
|
|
85
|
+
const manifestPath = path.join(outDir, 'manifest.json')
|
|
86
|
+
if (fs.existsSync(manifestPath)) {
|
|
87
|
+
const text = fs.readFileSync(manifestPath, 'utf8')
|
|
88
|
+
fs.writeFileSync(
|
|
89
|
+
manifestPath,
|
|
90
|
+
text.split(oldDomain).join(newDomain),
|
|
91
|
+
'utf8'
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const textExt = ['.js', '.css', '.html', '.json', '.map', '.svg']
|
|
96
|
+
const walkDir = (dir, cb) => {
|
|
97
|
+
if (!fs.existsSync(dir)) return
|
|
98
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
99
|
+
const full = path.join(dir, entry)
|
|
100
|
+
const st = fs.statSync(full)
|
|
101
|
+
if (st.isDirectory()) walkDir(full, cb)
|
|
102
|
+
else cb(full)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
walkDir(outDir, (file) => {
|
|
107
|
+
if (path.basename(file) === 'manifest.json') return
|
|
108
|
+
if (!textExt.includes(path.extname(file))) return
|
|
109
|
+
try {
|
|
110
|
+
const txt = fs.readFileSync(file, 'utf8')
|
|
111
|
+
if (txt.includes(oldDomain)) {
|
|
112
|
+
fs.writeFileSync(file, txt.split(oldDomain).join(newDomain), 'utf8')
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
/* ignore */
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const buildOnce = async ({ minify = false, local = false } = {}) => {
|
|
121
|
+
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true })
|
|
122
|
+
|
|
123
|
+
await Promise.all(
|
|
124
|
+
ENTRIES.map((e) =>
|
|
125
|
+
esbuild.build(
|
|
126
|
+
makeEsbuildOptions({
|
|
127
|
+
entry: e.in,
|
|
128
|
+
outfile: e.out,
|
|
129
|
+
minify,
|
|
130
|
+
sourcemap: !minify
|
|
131
|
+
})
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
console.log('Bundles written: content.js, settings_ui.js, service_worker.js')
|
|
136
|
+
copyStatic()
|
|
137
|
+
syncManifestVersion()
|
|
138
|
+
|
|
139
|
+
if (local) {
|
|
140
|
+
try {
|
|
141
|
+
rewriteUrls('https://symbols.app', 'http://localhost:1024')
|
|
142
|
+
console.log('Rewrote URLs for local development')
|
|
143
|
+
} catch (e) {
|
|
144
|
+
console.error('Error rewriting URLs:', e)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function debounce(fn, ms) {
|
|
150
|
+
let t = null
|
|
151
|
+
return (...args) => {
|
|
152
|
+
clearTimeout(t)
|
|
153
|
+
t = setTimeout(() => fn(...args), ms)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const run = async () => {
|
|
158
|
+
const args = process.argv.slice(2)
|
|
159
|
+
const watch = args.includes('--watch')
|
|
160
|
+
const minify =
|
|
161
|
+
args.includes('--prod') || process.env.NODE_ENV === 'production'
|
|
162
|
+
const local = args.includes('--local') || args.includes('-l')
|
|
163
|
+
|
|
164
|
+
if (!watch) {
|
|
165
|
+
try {
|
|
166
|
+
await buildOnce({ minify, local })
|
|
167
|
+
console.log('Build complete.')
|
|
168
|
+
process.exit(0)
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.error('Build failed:', err)
|
|
171
|
+
process.exit(1)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const chokidar = require('chokidar')
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
await buildOnce({ minify: false, local })
|
|
179
|
+
console.log('Watching for changes...')
|
|
180
|
+
|
|
181
|
+
const watchPaths = [path.join(srcDir, '**', '*.js'), staticDir]
|
|
182
|
+
|
|
183
|
+
const watcher = chokidar.watch(watchPaths, {
|
|
184
|
+
ignoreInitial: true,
|
|
185
|
+
persistent: true
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const debouncedBuild = debounce(async () => {
|
|
189
|
+
try {
|
|
190
|
+
console.log('Change detected — rebuilding...')
|
|
191
|
+
await buildOnce({ minify: false, local })
|
|
192
|
+
console.log('Rebuild complete')
|
|
193
|
+
} catch (e) {
|
|
194
|
+
console.error('Rebuild failed:', e)
|
|
195
|
+
}
|
|
196
|
+
}, 200)
|
|
197
|
+
|
|
198
|
+
watcher.on('all', () => debouncedBuild())
|
|
199
|
+
} catch (err) {
|
|
200
|
+
console.error('Watch failed:', err)
|
|
201
|
+
process.exit(1)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
run()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<style>
|
|
3
|
+
path { fill: #0085FF; }
|
|
4
|
+
</style>
|
|
5
|
+
<path d="M13.843 2.7C19.063 2.7 23 6.366 23 11.228c0 3.754-2.862 6.584-6.658 6.584-3.287 0-5.007-2.318-5.007-4.609 0-2.395 1.923-4.344 4.287-4.344.566 0 1.023.12 1.309.223a.212.212 0 01.137.229l-.016.058-.514 1.18a.223.223 0 01-.245.13 2.965 2.965 0 00-.506-.046c-1.245 0-2.258 1.027-2.258 2.288 0 1.33 1.165 2.373 2.651 2.373 2.195 0 3.913-1.777 3.913-4.046 0-3.024-2.294-5.135-5.58-5.135-4.076 0-7.393 3.36-7.393 7.491a7.519 7.519 0 002.871 5.924l-4.96 3.18A12.042 12.042 0 012 14.7c0-6.617 5.313-12 11.843-12z" fill-rule="evenodd"/>
|
|
6
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/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
|
+
})()
|
package/dist/content.css
ADDED
|
@@ -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
|
+
}
|