@antigenic-oss/paint 0.2.8 → 0.3.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/LICENSE +1 -1
- package/NOTICE +2 -2
- package/README.md +42 -15
- package/bin/paint.js +32 -0
- package/next.config.mjs +8 -0
- package/package.json +10 -8
- package/public/dev-editor-inspector.js +14 -0
- package/public/sw-proxy/sw.js +886 -0
- package/src/app/api/proxy/[[...path]]/route.ts +12 -1
- package/src/app/api/sw-fetch/[[...path]]/route.ts +149 -0
- package/src/app/docs/DocsClient.tsx +1 -1
- package/src/app/docs/page.tsx +134 -407
- package/src/app/layout.tsx +48 -2
- package/src/app/page.tsx +2 -0
- package/src/components/ConnectModal.tsx +98 -181
- package/src/components/PreviewFrame.tsx +91 -0
- package/src/components/TargetSelector.tsx +49 -15
- package/src/components/left-panel/LayerNode.tsx +5 -2
- package/src/components/left-panel/LayersPanel.tsx +10 -1
- package/src/components/right-panel/changes/ChangesPanel.tsx +7 -1
- package/src/hooks/useChangeTracker.ts +34 -26
- package/src/hooks/usePostMessage.ts +27 -1
- package/src/lib/serviceWorkerRegistration.ts +163 -0
- package/src/store/treeSlice.ts +29 -17
- package/src/store/uiSlice.ts +6 -0
- package/src/types/messages.ts +6 -0
|
@@ -1330,6 +1330,11 @@ function getInspectorCode(): string {
|
|
|
1330
1330
|
}
|
|
1331
1331
|
var meNewSelector = generateSelectorPath(meEl);
|
|
1332
1332
|
var meActualIndex = Array.from(meNewParent.children).indexOf(meEl);
|
|
1333
|
+
var meAttrs = {};
|
|
1334
|
+
for (var mai = 0; mai < meEl.attributes.length; mai++) {
|
|
1335
|
+
var ma = meEl.attributes[mai];
|
|
1336
|
+
meAttrs[ma.name] = ma.value;
|
|
1337
|
+
}
|
|
1333
1338
|
send({
|
|
1334
1339
|
type: 'ELEMENT_MOVED',
|
|
1335
1340
|
payload: {
|
|
@@ -1338,7 +1343,13 @@ function getInspectorCode(): string {
|
|
|
1338
1343
|
oldParentSelectorPath: meOldParentSelector,
|
|
1339
1344
|
newParentSelectorPath: msg.payload.newParentSelectorPath,
|
|
1340
1345
|
oldIndex: meOldIndex,
|
|
1341
|
-
newIndex: meActualIndex
|
|
1346
|
+
newIndex: meActualIndex,
|
|
1347
|
+
tagName: meEl.tagName.toLowerCase(),
|
|
1348
|
+
className: meEl.className && typeof meEl.className === 'string' ? meEl.className : null,
|
|
1349
|
+
elementId: meEl.id || null,
|
|
1350
|
+
innerText: (meEl.innerText || '').substring(0, 500) || null,
|
|
1351
|
+
attributes: meAttrs,
|
|
1352
|
+
computedStyles: getComputedStylesForElement(meEl)
|
|
1342
1353
|
}
|
|
1343
1354
|
});
|
|
1344
1355
|
if (selectedElement === meEl) {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { type NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight server-side proxy for the SW proxy.
|
|
5
|
+
* Fetches from the target localhost server and returns the raw response
|
|
6
|
+
* (no script stripping, no inspector injection — the SW handles all that).
|
|
7
|
+
* This exists because the SW runs in the browser and can't fetch cross-origin
|
|
8
|
+
* (localhost:3000) without CORS headers from the target.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const STRIP_HEADERS = new Set([
|
|
12
|
+
'content-encoding',
|
|
13
|
+
'transfer-encoding',
|
|
14
|
+
'cross-origin-embedder-policy',
|
|
15
|
+
'cross-origin-opener-policy',
|
|
16
|
+
'cross-origin-resource-policy',
|
|
17
|
+
'content-security-policy',
|
|
18
|
+
'content-security-policy-report-only',
|
|
19
|
+
'x-frame-options',
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
async function handler(
|
|
23
|
+
request: NextRequest,
|
|
24
|
+
{ params }: { params: Promise<{ path?: string[] }> },
|
|
25
|
+
) {
|
|
26
|
+
const targetUrl = request.headers.get('x-sw-target')
|
|
27
|
+
if (!targetUrl) {
|
|
28
|
+
return new NextResponse('Missing x-sw-target header', { status: 400 })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Validate localhost only
|
|
32
|
+
try {
|
|
33
|
+
const parsed = new URL(targetUrl)
|
|
34
|
+
if (
|
|
35
|
+
parsed.hostname !== 'localhost' &&
|
|
36
|
+
parsed.hostname !== '127.0.0.1'
|
|
37
|
+
) {
|
|
38
|
+
return new NextResponse('Only localhost targets allowed', { status: 403 })
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
return new NextResponse('Invalid target URL', { status: 400 })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { path } = await params
|
|
45
|
+
const targetPath = path ? `/${path.join('/')}` : '/'
|
|
46
|
+
const targetOrigin = new URL(targetUrl).origin
|
|
47
|
+
const search = request.nextUrl.search
|
|
48
|
+
const fetchUrl = targetOrigin + targetPath + search
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Forward all request headers to the target server. This ensures RSC
|
|
52
|
+
// headers (RSC, Next-Router-State-Tree, Next-Router-Prefetch), auth
|
|
53
|
+
// headers, Supabase headers, and any custom API headers reach the target.
|
|
54
|
+
// Only skip headers that must reflect the actual target or are internal.
|
|
55
|
+
const SKIP_REQUEST_HEADERS = new Set([
|
|
56
|
+
'host',
|
|
57
|
+
'origin',
|
|
58
|
+
'referer',
|
|
59
|
+
'connection',
|
|
60
|
+
'x-sw-target',
|
|
61
|
+
'x-forwarded-for',
|
|
62
|
+
'x-forwarded-host',
|
|
63
|
+
'x-forwarded-proto',
|
|
64
|
+
'x-forwarded-port',
|
|
65
|
+
'x-invoke-path',
|
|
66
|
+
'x-invoke-query',
|
|
67
|
+
'x-middleware-invoke',
|
|
68
|
+
'x-middleware-prefetch',
|
|
69
|
+
])
|
|
70
|
+
const forwardHeaders: Record<string, string> = {}
|
|
71
|
+
request.headers.forEach((value, key) => {
|
|
72
|
+
if (!SKIP_REQUEST_HEADERS.has(key.toLowerCase())) {
|
|
73
|
+
forwardHeaders[key] = value
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const init: RequestInit = {
|
|
78
|
+
method: request.method,
|
|
79
|
+
headers: forwardHeaders,
|
|
80
|
+
redirect: 'manual',
|
|
81
|
+
}
|
|
82
|
+
// Forward body for non-GET/HEAD requests
|
|
83
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
84
|
+
init.body = request.body
|
|
85
|
+
// @ts-expect-error -- Node fetch supports duplex for streaming body
|
|
86
|
+
init.duplex = 'half'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Follow redirects manually so we can track the final URL
|
|
90
|
+
let response = await fetch(fetchUrl, init)
|
|
91
|
+
let finalUrl: string | null = null
|
|
92
|
+
let redirectCount = 0
|
|
93
|
+
while (
|
|
94
|
+
redirectCount < 10 &&
|
|
95
|
+
response.status >= 300 &&
|
|
96
|
+
response.status < 400 &&
|
|
97
|
+
response.headers.get('location')
|
|
98
|
+
) {
|
|
99
|
+
const location = response.headers.get('location')!
|
|
100
|
+
const redirectTo = new URL(location, fetchUrl).href
|
|
101
|
+
finalUrl = redirectTo
|
|
102
|
+
response = await fetch(redirectTo, {
|
|
103
|
+
headers: forwardHeaders,
|
|
104
|
+
redirect: 'manual',
|
|
105
|
+
})
|
|
106
|
+
redirectCount++
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Copy headers, stripping security headers
|
|
110
|
+
const responseHeaders = new Headers()
|
|
111
|
+
for (const [key, value] of response.headers.entries()) {
|
|
112
|
+
if (!STRIP_HEADERS.has(key.toLowerCase())) {
|
|
113
|
+
responseHeaders.set(key, value)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
responseHeaders.delete('content-length')
|
|
117
|
+
|
|
118
|
+
// Forward set-cookie headers from the target (auth tokens, sessions)
|
|
119
|
+
const setCookies = response.headers.getSetCookie?.()
|
|
120
|
+
if (setCookies?.length) {
|
|
121
|
+
for (const sc of setCookies) {
|
|
122
|
+
responseHeaders.append('set-cookie', sc)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// If the target redirected, tell the SW the final URL so the navigation
|
|
127
|
+
// blocker can set the correct path (prevents hydration mismatch).
|
|
128
|
+
if (finalUrl) {
|
|
129
|
+
responseHeaders.set('x-sw-final-url', finalUrl)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return new NextResponse(response.body, {
|
|
133
|
+
status: response.status,
|
|
134
|
+
headers: responseHeaders,
|
|
135
|
+
})
|
|
136
|
+
} catch (err) {
|
|
137
|
+
return new NextResponse(
|
|
138
|
+
`Failed to fetch from ${fetchUrl}: ${err instanceof Error ? err.message : String(err)}`,
|
|
139
|
+
{ status: 502 },
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const GET = handler
|
|
145
|
+
export const POST = handler
|
|
146
|
+
export const PUT = handler
|
|
147
|
+
export const PATCH = handler
|
|
148
|
+
export const DELETE = handler
|
|
149
|
+
export const OPTIONS = handler
|
|
@@ -6,7 +6,7 @@ const NAV_ITEMS = [
|
|
|
6
6
|
{ id: 'how-it-works', label: 'How It Works' },
|
|
7
7
|
{ id: 'use-cases', label: 'Use Cases' },
|
|
8
8
|
{ id: 'quick-start', label: 'Quick Start' },
|
|
9
|
-
{ id: 'framework-guides', label: '
|
|
9
|
+
{ id: 'framework-guides', label: 'Compatibility' },
|
|
10
10
|
{ id: 'troubleshooting', label: 'Troubleshooting' },
|
|
11
11
|
{ id: 'faq', label: 'FAQ' },
|
|
12
12
|
] as const
|