@designfever/web-review-kit 0.7.0 → 0.7.2
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/.env.sample +3 -0
- package/README.md +1 -1
- package/dist/{chunk-AB5B6O77.js → chunk-K26WLRRP.js} +40 -8
- package/dist/chunk-K26WLRRP.js.map +1 -0
- package/dist/index.cjs +336 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -1
- package/dist/index.d.ts +68 -1
- package/dist/index.js +298 -1
- package/dist/index.js.map +1 -1
- package/dist/react-shell.cjs +89 -26
- package/dist/react-shell.cjs.map +1 -1
- package/dist/react-shell.d.cts +2 -0
- package/dist/react-shell.d.ts +2 -0
- package/dist/react-shell.js +89 -26
- package/dist/react-shell.js.map +1 -1
- package/dist/vite.cjs +44 -16
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.js +46 -17
- package/dist/vite.js.map +1 -1
- package/docs/README.md +6 -2
- package/docs/adapters.md +33 -3
- package/docs/figma-image-mvp-0.7.0.md +15 -12
- package/docs/figma-overlay.md +11 -4
- package/docs/installation.md +3 -0
- package/docs/release-notes-0.7.0.md +6 -2
- package/docs/release-notes-0.7.1.md +34 -0
- package/docs/release-notes-0.7.2.md +33 -0
- package/package.json +1 -1
- package/dist/chunk-AB5B6O77.js.map +0 -1
package/.env.sample
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
# Review project id. Used for storage keys, remote item filtering, and presence.
|
|
6
6
|
VITE_REVIEW_PROJECT_ID=my-project
|
|
7
7
|
|
|
8
|
+
# Optional default reviewer id. Settings localStorage user-id still wins.
|
|
9
|
+
VITE_REVIEW_USER_ID=
|
|
10
|
+
|
|
8
11
|
# Optional server/dev-only Figma image rendering token.
|
|
9
12
|
# This must stay in the dev/server process env. Do not expose it as VITE_*.
|
|
10
13
|
FIGMA_TOKEN=
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ This package does not own internal operator tools, private admin keys, or produc
|
|
|
28
28
|
- [Architecture and runtime logic](docs/architecture.md): core runtime, React shell, coordinate, anchor, and extension boundaries.
|
|
29
29
|
- [Figma overlay](docs/figma-overlay.md): how the shell toggles a host Figma overlay.
|
|
30
30
|
- [Grid overlay](docs/grid-overlay.md): how the shell toggles a host grid/helper overlay.
|
|
31
|
-
- [Release notes 0.7.
|
|
31
|
+
- [Release notes 0.7.2](docs/release-notes-0.7.2.md): latest remote Figma image store changes.
|
|
32
32
|
|
|
33
33
|
## Quick Start
|
|
34
34
|
|
|
@@ -234,7 +234,10 @@ function createReviewFigmaImageStoreClient(options = {}) {
|
|
|
234
234
|
);
|
|
235
235
|
return request(endpoint, {
|
|
236
236
|
method: "POST",
|
|
237
|
-
body: JSON.stringify(nextInput)
|
|
237
|
+
body: JSON.stringify(nextInput),
|
|
238
|
+
figmaToken: readReviewFigmaImageToken(
|
|
239
|
+
options.token ?? getStoredReviewFigmaImageToken
|
|
240
|
+
)
|
|
238
241
|
});
|
|
239
242
|
},
|
|
240
243
|
updateImage(id, patch) {
|
|
@@ -262,10 +265,12 @@ async function createClientRenderedAddImageInput(input, clientRender, fetchOptio
|
|
|
262
265
|
const token = readClientRenderToken(options);
|
|
263
266
|
if (!token) return input;
|
|
264
267
|
try {
|
|
265
|
-
const asset = await
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
268
|
+
const asset = await createReviewFigmaClientRenderedAsset({
|
|
269
|
+
...options,
|
|
270
|
+
fetch: fetchOption,
|
|
271
|
+
figmaUrl: input.figmaUrl,
|
|
272
|
+
token
|
|
273
|
+
});
|
|
269
274
|
return {
|
|
270
275
|
...input,
|
|
271
276
|
imageFormat: asset.imageFormat,
|
|
@@ -337,6 +342,17 @@ async function createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOpt
|
|
|
337
342
|
height: dimensions.height
|
|
338
343
|
};
|
|
339
344
|
}
|
|
345
|
+
function createReviewFigmaClientRenderedAsset({
|
|
346
|
+
fetch: fetchOption,
|
|
347
|
+
figmaUrl,
|
|
348
|
+
token,
|
|
349
|
+
...options
|
|
350
|
+
}) {
|
|
351
|
+
return withTimeout(
|
|
352
|
+
createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOption),
|
|
353
|
+
options.timeoutMs ?? 1e4
|
|
354
|
+
);
|
|
355
|
+
}
|
|
340
356
|
async function readImageBlobDimensions(blob) {
|
|
341
357
|
const image = await loadImageBlob(blob);
|
|
342
358
|
return {
|
|
@@ -427,11 +443,14 @@ function createReviewFigmaImageStoreRequest(endpoint, fetchOption) {
|
|
|
427
443
|
return async (input, init = {}) => {
|
|
428
444
|
const requestFetch = fetchOption ?? globalThis.fetch;
|
|
429
445
|
if (!requestFetch) throw new Error("Figma image store requires fetch.");
|
|
446
|
+
const figmaToken = init.figmaToken ?? "";
|
|
447
|
+
const { figmaToken: _figmaToken, ...requestInit } = init;
|
|
430
448
|
const response = await requestFetch(input, {
|
|
431
|
-
...
|
|
449
|
+
...requestInit,
|
|
432
450
|
headers: {
|
|
433
451
|
"Content-Type": "application/json",
|
|
434
|
-
...
|
|
452
|
+
...figmaToken ? { "X-Figma-Token": figmaToken } : {},
|
|
453
|
+
...requestInit.headers ?? {}
|
|
435
454
|
}
|
|
436
455
|
});
|
|
437
456
|
const text = await response.text();
|
|
@@ -443,6 +462,18 @@ function createReviewFigmaImageStoreRequest(endpoint, fetchOption) {
|
|
|
443
462
|
return body;
|
|
444
463
|
};
|
|
445
464
|
}
|
|
465
|
+
function readReviewFigmaImageToken(provider) {
|
|
466
|
+
const token = typeof provider === "function" ? provider() : provider;
|
|
467
|
+
return typeof token === "string" ? token.trim() : "";
|
|
468
|
+
}
|
|
469
|
+
function getStoredReviewFigmaImageToken() {
|
|
470
|
+
if (typeof window === "undefined") return "";
|
|
471
|
+
try {
|
|
472
|
+
return window.localStorage.getItem("figma-token") ?? "";
|
|
473
|
+
} catch {
|
|
474
|
+
return "";
|
|
475
|
+
}
|
|
476
|
+
}
|
|
446
477
|
function normalizeReviewFigmaImageTarget(target) {
|
|
447
478
|
if (target.type === "figma-node") {
|
|
448
479
|
return {
|
|
@@ -575,10 +606,11 @@ export {
|
|
|
575
606
|
createReviewFigmaImageApiUrl,
|
|
576
607
|
DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT,
|
|
577
608
|
createReviewFigmaImageStoreClient,
|
|
609
|
+
createReviewFigmaClientRenderedAsset,
|
|
578
610
|
getReviewFigmaImageTargetKey,
|
|
579
611
|
getReviewFigmaImageMimeType,
|
|
580
612
|
createReviewFigmaImagesSnapshot,
|
|
581
613
|
createReviewFigmaReleaseSnapshot,
|
|
582
614
|
collectReviewFigmaReleaseSnapshot
|
|
583
615
|
};
|
|
584
|
-
//# sourceMappingURL=chunk-
|
|
616
|
+
//# sourceMappingURL=chunk-K26WLRRP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/figma/parse.ts","../src/figma/token.ts","../src/figma/render.ts","../src/vite/figma-asset.ts","../src/figma/image.store.ts","../src/figma/image.snapshot.ts"],"sourcesContent":["export type ReviewFigmaNodeRef = {\n fileKey: string;\n nodeId: string;\n sourceUrl?: string;\n};\n\nexport const FIGMA_NODE_REF_SEPARATOR = '->';\n\nexport function parseReviewFigmaNodeRef(\n value: string | ReviewFigmaNodeRef\n): ReviewFigmaNodeRef | null {\n if (typeof value !== 'string') return normalizeReviewFigmaNodeRef(value);\n\n const input = value.trim();\n if (!input) return null;\n\n return parseReviewFigmaNodeRefValue(input) ?? parseReviewFigmaUrl(input);\n}\n\nexport function requireReviewFigmaNodeRef(\n value: string | ReviewFigmaNodeRef\n): ReviewFigmaNodeRef {\n const ref = parseReviewFigmaNodeRef(value);\n if (!ref) {\n throw new Error('A Figma node link or fileKey->nodeId value is required.');\n }\n return ref;\n}\n\nexport function createReviewFigmaNodeValue(ref: ReviewFigmaNodeRef) {\n return `${ref.fileKey}${FIGMA_NODE_REF_SEPARATOR}${ref.nodeId}`;\n}\n\nexport function createReviewFigmaFrameUrl(\n value: string | ReviewFigmaNodeRef\n) {\n const ref = parseReviewFigmaNodeRef(value);\n if (!ref) return null;\n\n return `https://www.figma.com/design/${encodeURIComponent(\n ref.fileKey\n )}?node-id=${encodeURIComponent(ref.nodeId.replace(/:/g, '-'))}`;\n}\n\nfunction parseReviewFigmaNodeRefValue(value: string) {\n const [fileKey, nodeId, extra] = value\n .split(FIGMA_NODE_REF_SEPARATOR)\n .map((part) => part.trim());\n\n if (!fileKey || !nodeId || extra !== undefined) return null;\n\n return normalizeReviewFigmaNodeRef({ fileKey, nodeId });\n}\n\nfunction parseReviewFigmaUrl(value: string) {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return null;\n }\n\n const fileKey = getFigmaFileKey(url);\n const nodeId = normalizeFigmaNodeId(url.searchParams.get('node-id'));\n if (!fileKey || !nodeId) return null;\n\n return { fileKey, nodeId, sourceUrl: value };\n}\n\nfunction getFigmaFileKey(url: URL) {\n if (!/(^|\\.)figma\\.com$/i.test(url.hostname)) return null;\n\n const parts = url.pathname.split('/').filter(Boolean);\n const fileKeyIndex = parts.findIndex((part) =>\n ['design', 'file', 'proto', 'board'].includes(part)\n );\n const fileKey = fileKeyIndex >= 0 ? parts[fileKeyIndex + 1] : null;\n\n return normalizeFigmaFileKey(fileKey);\n}\n\nfunction normalizeReviewFigmaNodeRef(\n value: ReviewFigmaNodeRef | null | undefined\n) {\n const fileKey = normalizeFigmaFileKey(value?.fileKey);\n const nodeId = normalizeFigmaNodeId(value?.nodeId);\n if (!fileKey || !nodeId) return null;\n const sourceUrl = normalizeOptionalString(value?.sourceUrl);\n\n return sourceUrl ? { fileKey, nodeId, sourceUrl } : { fileKey, nodeId };\n}\n\nfunction normalizeFigmaFileKey(value: string | null | undefined) {\n return normalizeOptionalString(value)?.replace(/[?#].*$/, '') ?? null;\n}\n\nfunction normalizeFigmaNodeId(value: string | null | undefined) {\n const nodeId = normalizeOptionalString(value);\n if (!nodeId) return null;\n if (nodeId.includes(':')) return nodeId;\n return nodeId.replace(/-/g, ':');\n}\n\nfunction normalizeOptionalString(value: string | null | undefined) {\n if (typeof value !== 'string') return null;\n return value.trim() || null;\n}\n","export const DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY = 'FIGMA_TOKEN';\nexport const REVIEW_FIGMA_TOKEN_MISSING_CODE = 'DFWR_FIGMA_TOKEN_MISSING';\n\nexport type ReviewFigmaTokenEnv = Record<\n string,\n string | null | undefined\n>;\n\nexport type ReviewFigmaTokenOptions = {\n token?: string | null;\n env?: ReviewFigmaTokenEnv;\n envKey?: string;\n enabled?: boolean;\n};\n\nexport class ReviewFigmaTokenError extends Error {\n readonly code = REVIEW_FIGMA_TOKEN_MISSING_CODE;\n readonly envKey: string;\n\n constructor(envKey = DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY) {\n super(\n `Figma image rendering requires server env ${envKey}. Set ${envKey} in the dev/server environment; do not expose it as VITE_${envKey}.`\n );\n this.name = 'ReviewFigmaTokenError';\n this.envKey = envKey;\n }\n}\n\nexport function readReviewFigmaToken(\n options: ReviewFigmaTokenOptions = {}\n): string | null {\n if (options.enabled === false) return null;\n\n const envKey = options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY;\n return normalizeReviewFigmaToken(options.token ?? options.env?.[envKey]);\n}\n\nexport function requireReviewFigmaToken(\n options: ReviewFigmaTokenOptions = {}\n): string {\n const envKey = options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY;\n const token = readReviewFigmaToken(options);\n\n if (!token) throw new ReviewFigmaTokenError(envKey);\n return token;\n}\n\nexport function isReviewFigmaTokenError(\n error: unknown\n): error is ReviewFigmaTokenError {\n if (!error || typeof error !== 'object') return false;\n\n return (\n error instanceof ReviewFigmaTokenError ||\n ('code' in error && error.code === REVIEW_FIGMA_TOKEN_MISSING_CODE)\n );\n}\n\nfunction normalizeReviewFigmaToken(value: string | null | undefined) {\n if (typeof value !== 'string') return null;\n return value.trim() || null;\n}\n","import {\n requireReviewFigmaNodeRef,\n type ReviewFigmaNodeRef,\n} from './parse';\nimport { requireReviewFigmaToken } from './token';\n\nexport type ReviewFigmaRenderFormat = 'png' | 'jpg' | 'svg' | 'pdf';\n\nexport type ReviewFigmaImageRenderOptions = {\n figmaUrl: string | ReviewFigmaNodeRef;\n token?: string | null;\n format?: ReviewFigmaRenderFormat;\n scale?: number;\n useAbsoluteBounds?: boolean;\n apiBaseUrl?: string;\n fetch?: typeof fetch;\n signal?: AbortSignal;\n};\n\nexport type ReviewFigmaRenderedImage = {\n fileKey: string;\n nodeId: string;\n figmaUrl?: string;\n imageUrl: string;\n renderFormat: ReviewFigmaRenderFormat;\n};\n\ntype FigmaImageResponse = {\n err?: string | null;\n images?: Record<string, string | null | undefined>;\n};\n\nconst DEFAULT_FIGMA_API_BASE_URL = 'https://api.figma.com';\n\nexport async function renderReviewFigmaImage(\n options: ReviewFigmaImageRenderOptions\n): Promise<ReviewFigmaRenderedImage> {\n const token = requireReviewFigmaToken({ token: options.token });\n const ref = requireReviewFigmaNodeRef(options.figmaUrl);\n const renderFormat = options.format ?? 'png';\n const requestUrl = createReviewFigmaImageApiUrl({\n apiBaseUrl: options.apiBaseUrl,\n fileKey: ref.fileKey,\n nodeId: ref.nodeId,\n format: renderFormat,\n scale: options.scale,\n useAbsoluteBounds: options.useAbsoluteBounds,\n });\n const fetchImage = options.fetch ?? globalThis.fetch;\n if (!fetchImage) throw new Error('Figma image rendering requires fetch.');\n\n const response = await fetchImage(requestUrl, {\n headers: {\n 'X-Figma-Token': token,\n },\n signal: options.signal,\n });\n const body = (await response.json().catch(() => null)) as\n | FigmaImageResponse\n | null;\n\n if (!response.ok) {\n throw new Error(\n body?.err || `Figma image render failed with ${response.status}`\n );\n }\n\n const imageUrl = body?.images?.[ref.nodeId];\n if (!imageUrl) {\n throw new Error(`Figma image render returned no URL for ${ref.nodeId}.`);\n }\n\n return {\n fileKey: ref.fileKey,\n nodeId: ref.nodeId,\n figmaUrl: typeof options.figmaUrl === 'string'\n ? options.figmaUrl\n : options.figmaUrl.sourceUrl,\n imageUrl,\n renderFormat,\n };\n}\n\nexport function createReviewFigmaImageApiUrl({\n apiBaseUrl = DEFAULT_FIGMA_API_BASE_URL,\n fileKey,\n nodeId,\n format = 'png',\n scale,\n useAbsoluteBounds,\n}: {\n apiBaseUrl?: string;\n fileKey: string;\n nodeId: string;\n format?: ReviewFigmaRenderFormat;\n scale?: number;\n useAbsoluteBounds?: boolean;\n}) {\n const url = new URL(`/v1/images/${encodeURIComponent(fileKey)}`, apiBaseUrl);\n url.searchParams.set('ids', nodeId);\n url.searchParams.set('format', format);\n\n if (typeof scale === 'number' && Number.isFinite(scale) && scale > 0) {\n url.searchParams.set('scale', String(scale));\n }\n if (useAbsoluteBounds !== undefined) {\n url.searchParams.set('use_absolute_bounds', String(useAbsoluteBounds));\n }\n\n return url.toString();\n}\n","// Pure helpers for Figma image asset format conversion and storage-key handling.\n// Extracted from src/vite.ts to keep the plugin entry focused on request flow.\nimport type { ReviewFigmaImageFormat } from '../figma/image.types';\nimport type { ReviewFigmaRenderFormat } from '../figma/render';\n\nexport function parseReviewFigmaImageFormat(value: unknown) {\n return value === 'webp' || value === 'png' || value === 'jpg'\n ? value\n : undefined;\n}\n\nexport function getStoreRenderFormat(\n renderFormat: ReviewFigmaRenderFormat | undefined,\n imageFormat: ReviewFigmaImageFormat | undefined\n): Extract<ReviewFigmaRenderFormat, 'png' | 'jpg'> {\n if (renderFormat === 'jpg' || renderFormat === 'png') return renderFormat;\n if (imageFormat === 'jpg') return 'jpg';\n return 'png';\n}\n\nexport function getReviewFigmaImageFormatFromMimeType(\n mimeType: string\n): ReviewFigmaImageFormat | null {\n if (mimeType === 'image/webp') return 'webp';\n if (mimeType === 'image/png') return 'png';\n if (mimeType === 'image/jpeg') return 'jpg';\n return null;\n}\n\nexport function normalizeImageMimeType(value: string | null | undefined) {\n const mimeType = value?.split(';')[0]?.trim().toLowerCase();\n if (mimeType === 'image/jpg') return 'image/jpeg';\n if (\n mimeType === 'image/jpeg' ||\n mimeType === 'image/png' ||\n mimeType === 'image/webp'\n ) {\n return mimeType;\n }\n return null;\n}\n\nexport function createReviewFigmaAssetStorageKey(\n id: string,\n imageFormat: ReviewFigmaImageFormat\n) {\n return `${id}.${getReviewFigmaAssetExtension(imageFormat)}`;\n}\n\nexport function createReviewFigmaAssetUrl(\n assetEndpoint: string,\n storageKey: string\n) {\n return `${assetEndpoint}/${encodeURIComponent(storageKey)}`;\n}\n\nexport function getReviewFigmaAssetStorageKeyFromPathname(\n pathname: string,\n assetEndpoint: string\n) {\n try {\n const storageKey = decodeURIComponent(\n pathname.slice(assetEndpoint.length + 1)\n );\n return isSafeReviewFigmaAssetStorageKey(storageKey) ? storageKey : null;\n } catch {\n return null;\n }\n}\n\nexport function isSafeReviewFigmaAssetStorageKey(value: string) {\n return /^figma_[a-z0-9_]+\\.(webp|png|jpg)$/.test(value);\n}\n\nfunction getReviewFigmaAssetExtension(format: ReviewFigmaImageFormat) {\n return format === 'jpg' ? 'jpg' : format;\n}\n\nexport function getReviewFigmaAssetMimeType(storageKey: string) {\n if (storageKey.endsWith('.jpg')) return 'image/jpeg';\n if (storageKey.endsWith('.webp')) return 'image/webp';\n return 'image/png';\n}\n","import type {\n AddReviewFigmaImageInput,\n ReorderReviewFigmaImagesInput,\n ReviewFigmaImage,\n ReviewFigmaImageAssetInput,\n ReviewFigmaImageFormat,\n ReviewFigmaImageStore,\n ReviewFigmaImageTarget,\n} from './image.types';\nimport { getReviewFigmaImageFormatFromMimeType } from '../vite/figma-asset';\nimport { parseReviewFigmaNodeRef } from './parse';\nimport {\n createReviewFigmaImageApiUrl,\n type ReviewFigmaRenderFormat,\n} from './render';\n\nexport const DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT =\n '/__dfwr/figma-images';\n\ntype ReviewFigmaImageTokenProvider =\n | string\n | null\n | undefined\n | (() => string | null | undefined);\n\nexport type ReviewFigmaImageStoreClientOptions = {\n endpoint?: string;\n fetch?: typeof fetch;\n token?: ReviewFigmaImageTokenProvider;\n clientRender?: boolean | ReviewFigmaImageClientRenderOptions;\n};\n\nexport type ReviewFigmaImageClientRenderOptions = {\n token?: string | null | (() => string | null | undefined);\n apiBaseUrl?: string;\n renderFormat?: Extract<ReviewFigmaRenderFormat, 'png' | 'jpg'>;\n renderScale?: number;\n useAbsoluteBounds?: boolean;\n convertToWebp?: boolean;\n webpQuality?: number;\n timeoutMs?: number;\n};\n\nexport type CreateReviewFigmaClientRenderedAssetOptions =\n ReviewFigmaImageClientRenderOptions & {\n figmaUrl: string;\n token: string;\n fetch?: typeof fetch;\n };\n\nexport function createReviewFigmaImageStoreClient(\n options: ReviewFigmaImageStoreClientOptions = {}\n): ReviewFigmaImageStore {\n const endpoint =\n options.endpoint ?? DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT;\n const request = createReviewFigmaImageStoreRequest(endpoint, options.fetch);\n\n return {\n listImages(target) {\n const url = `${endpoint}?target=${encodeURIComponent(\n JSON.stringify(target)\n )}`;\n return request<ReviewFigmaImage[]>(url);\n },\n async addImage(input) {\n const nextInput = await createClientRenderedAddImageInput(\n input,\n options.clientRender,\n options.fetch\n );\n return request<ReviewFigmaImage>(endpoint, {\n method: 'POST',\n body: JSON.stringify(nextInput),\n figmaToken: readReviewFigmaImageToken(\n options.token ?? getStoredReviewFigmaImageToken\n ),\n });\n },\n updateImage(id, patch) {\n return request<ReviewFigmaImage>(`${endpoint}/${encodeURIComponent(id)}`, {\n method: 'PATCH',\n body: JSON.stringify(patch),\n });\n },\n reorderImages(input) {\n return request<ReviewFigmaImage[]>(`${endpoint}/reorder`, {\n method: 'PATCH',\n body: JSON.stringify(input),\n });\n },\n deleteImage(id) {\n return request<void>(`${endpoint}/${encodeURIComponent(id)}`, {\n method: 'DELETE',\n });\n },\n };\n}\n\nasync function createClientRenderedAddImageInput(\n input: AddReviewFigmaImageInput,\n clientRender: ReviewFigmaImageStoreClientOptions['clientRender'],\n fetchOption: typeof fetch | undefined\n): Promise<AddReviewFigmaImageInput> {\n const options = normalizeClientRenderOptions(clientRender);\n if (!options) return input;\n\n const token = readClientRenderToken(options);\n if (!token) return input;\n\n try {\n const asset = await createReviewFigmaClientRenderedAsset({\n ...options,\n fetch: fetchOption,\n figmaUrl: input.figmaUrl,\n token,\n });\n return {\n ...input,\n imageFormat: asset.imageFormat,\n asset,\n };\n } catch {\n return input;\n }\n}\n\nfunction normalizeClientRenderOptions(\n clientRender: ReviewFigmaImageStoreClientOptions['clientRender']\n): ReviewFigmaImageClientRenderOptions | null {\n if (!clientRender) return null;\n if (clientRender === true) return {};\n return clientRender;\n}\n\nfunction readClientRenderToken(options: ReviewFigmaImageClientRenderOptions) {\n const token =\n typeof options.token === 'function' ? options.token() : options.token;\n return typeof token === 'string' ? token.trim() : '';\n}\n\nasync function createClientRenderedFigmaAsset(\n figmaUrl: string,\n token: string,\n options: ReviewFigmaImageClientRenderOptions,\n fetchOption: typeof fetch | undefined\n): Promise<ReviewFigmaImageAssetInput> {\n const ref = parseReviewFigmaNodeRef(figmaUrl);\n if (!ref) throw new Error('A Figma node link is required.');\n\n const requestFetch = fetchOption ?? globalThis.fetch;\n if (!requestFetch) throw new Error('Figma client rendering requires fetch.');\n\n const renderFormat = options.renderFormat ?? 'png';\n const response = await requestFetch(\n createReviewFigmaImageApiUrl({\n apiBaseUrl: options.apiBaseUrl,\n fileKey: ref.fileKey,\n nodeId: ref.nodeId,\n format: renderFormat,\n scale: options.renderScale,\n useAbsoluteBounds: options.useAbsoluteBounds,\n }),\n {\n headers: {\n 'X-Figma-Token': token,\n },\n }\n );\n const body = (await response.json().catch(() => null)) as\n | { err?: string | null; images?: Record<string, string | null | undefined> }\n | null;\n if (!response.ok) {\n throw new Error(body?.err || `Figma image render failed with ${response.status}`);\n }\n\n const imageUrl = body?.images?.[ref.nodeId];\n if (!imageUrl) throw new Error(`Figma image render returned no URL for ${ref.nodeId}.`);\n\n const imageResponse = await requestFetch(imageUrl);\n if (!imageResponse.ok) {\n throw new Error(`Figma image download failed with ${imageResponse.status}`);\n }\n\n const originalBlob = await imageResponse.blob();\n const originalMimeType =\n normalizeClientImageMimeType(originalBlob.type) ??\n getReviewFigmaImageMimeType(renderFormat === 'jpg' ? 'jpg' : 'png');\n const originalFormat =\n getReviewFigmaImageFormatFromMimeType(originalMimeType) ??\n (renderFormat === 'jpg' ? 'jpg' : 'png');\n const dimensions = await readImageBlobDimensions(originalBlob);\n const shouldConvertToWebp = options.convertToWebp ?? true;\n const convertedBlob = shouldConvertToWebp\n ? await convertImageBlobToWebp(\n originalBlob,\n options.webpQuality ?? 0.9,\n dimensions\n ).catch(() => null)\n : null;\n const finalBlob =\n convertedBlob?.type === 'image/webp' ? convertedBlob : originalBlob;\n const finalMimeType =\n normalizeClientImageMimeType(finalBlob.type) ?? originalMimeType;\n const finalFormat =\n getReviewFigmaImageFormatFromMimeType(finalMimeType) ?? originalFormat;\n\n return {\n dataUrl: await blobToDataUrl(finalBlob),\n imageFormat: finalFormat,\n mimeType: finalMimeType,\n byteSize: finalBlob.size,\n width: dimensions.width,\n height: dimensions.height,\n };\n}\n\nexport function createReviewFigmaClientRenderedAsset({\n fetch: fetchOption,\n figmaUrl,\n token,\n ...options\n}: CreateReviewFigmaClientRenderedAssetOptions): Promise<ReviewFigmaImageAssetInput> {\n return withTimeout(\n createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOption),\n options.timeoutMs ?? 10000\n );\n}\n\nasync function readImageBlobDimensions(blob: Blob) {\n const image = await loadImageBlob(blob);\n return {\n width: image.naturalWidth || image.width,\n height: image.naturalHeight || image.height,\n };\n}\n\nasync function convertImageBlobToWebp(\n blob: Blob,\n quality: number,\n dimensions: { width: number; height: number }\n) {\n if (typeof document === 'undefined') return null;\n if (!dimensions.width || !dimensions.height) return null;\n\n const image = await loadImageBlob(blob);\n const canvas = document.createElement('canvas');\n canvas.width = dimensions.width;\n canvas.height = dimensions.height;\n const context = canvas.getContext('2d');\n if (!context) return null;\n\n context.drawImage(image, 0, 0);\n\n return new Promise<Blob | null>((resolve) => {\n canvas.toBlob(resolve, 'image/webp', quality);\n });\n}\n\nfunction loadImageBlob(blob: Blob) {\n return new Promise<HTMLImageElement>((resolve, reject) => {\n if (typeof Image === 'undefined' || typeof URL === 'undefined') {\n reject(new Error('Image decoding is unavailable.'));\n return;\n }\n\n const image = new Image();\n const objectUrl = URL.createObjectURL(blob);\n image.onload = () => {\n URL.revokeObjectURL(objectUrl);\n resolve(image);\n };\n image.onerror = () => {\n URL.revokeObjectURL(objectUrl);\n reject(new Error('Image decoding failed.'));\n };\n image.src = objectUrl;\n });\n}\n\nfunction blobToDataUrl(blob: Blob) {\n return new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => {\n if (typeof reader.result === 'string') {\n resolve(reader.result);\n return;\n }\n reject(new Error('Blob encoding failed.'));\n };\n reader.onerror = () => reject(reader.error ?? new Error('Blob encoding failed.'));\n reader.readAsDataURL(blob);\n });\n}\n\nfunction normalizeClientImageMimeType(value: string | null | undefined) {\n const mimeType = value?.split(';')[0]?.trim().toLowerCase();\n if (mimeType === 'image/jpg') return 'image/jpeg';\n if (\n mimeType === 'image/jpeg' ||\n mimeType === 'image/png' ||\n mimeType === 'image/webp'\n ) {\n return mimeType;\n }\n return null;\n}\n\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number) {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<T>((_, reject) => {\n timeoutId = setTimeout(\n () => reject(new Error('Figma client rendering timed out.')),\n timeoutMs\n );\n }),\n ]);\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n }\n}\n\nexport function getReviewFigmaImageTargetKey(target: ReviewFigmaImageTarget) {\n return JSON.stringify(normalizeReviewFigmaImageTarget(target));\n}\n\nexport function getReviewFigmaImageMimeType(\n format: ReviewFigmaImageFormat\n) {\n if (format === 'jpg') return 'image/jpeg';\n if (format === 'png') return 'image/png';\n return 'image/webp';\n}\n\ntype ReviewFigmaImageStoreRequestInit = RequestInit & {\n figmaToken?: string;\n};\n\nfunction createReviewFigmaImageStoreRequest(\n endpoint: string,\n fetchOption: typeof fetch | undefined\n) {\n return async <T>(\n input: string,\n init: ReviewFigmaImageStoreRequestInit = {}\n ) => {\n const requestFetch = fetchOption ?? globalThis.fetch;\n if (!requestFetch) throw new Error('Figma image store requires fetch.');\n const figmaToken = init.figmaToken ?? '';\n const { figmaToken: _figmaToken, ...requestInit } = init;\n\n const response = await requestFetch(input, {\n ...requestInit,\n headers: {\n 'Content-Type': 'application/json',\n ...(figmaToken ? { 'X-Figma-Token': figmaToken } : {}),\n ...(requestInit.headers ?? {}),\n },\n });\n const text = await response.text();\n const body = text ? JSON.parse(text) : null;\n\n if (!response.ok) {\n const message =\n typeof body?.error === 'string'\n ? body.error\n : `Figma image store request failed: ${response.status}`;\n throw new Error(message);\n }\n\n return body as T;\n };\n}\n\nfunction readReviewFigmaImageToken(provider: ReviewFigmaImageTokenProvider) {\n const token = typeof provider === 'function' ? provider() : provider;\n return typeof token === 'string' ? token.trim() : '';\n}\n\nfunction getStoredReviewFigmaImageToken() {\n if (typeof window === 'undefined') return '';\n\n try {\n return window.localStorage.getItem('figma-token') ?? '';\n } catch {\n return '';\n }\n}\n\nfunction normalizeReviewFigmaImageTarget(target: ReviewFigmaImageTarget) {\n if (target.type === 'figma-node') {\n return {\n type: target.type,\n projectId: target.projectId,\n fileKey: target.fileKey,\n nodeId: target.nodeId,\n };\n }\n\n return {\n type: target.type,\n projectId: target.projectId,\n pageUrl: target.pageUrl,\n slot: target.slot ?? '',\n viewport: target.viewport\n ? {\n label: target.viewport.label ?? '',\n width: target.viewport.width ?? null,\n height: target.viewport.height ?? null,\n scope: target.viewport.scope ?? '',\n }\n : null,\n };\n}\n\nexport type { ReorderReviewFigmaImagesInput };\n","import type {\n ReviewFigmaImage,\n ReviewFigmaImageStore,\n ReviewFigmaImageTarget,\n} from './image.types';\nimport { getReviewFigmaImageTargetKey } from './image.store';\n\nexport type ReviewFigmaImagesSnapshot = ReviewFigmaImage[];\n\nexport type ReviewFigmaReleaseSnapshot = {\n version: 1;\n projectId: string;\n releaseId?: string;\n label?: string;\n createdAt: string;\n figmaImagesSnapshot: ReviewFigmaImagesSnapshot;\n};\n\nexport type CreateReviewFigmaImagesSnapshotOptions = {\n projectId?: string;\n targets?: readonly ReviewFigmaImageTarget[];\n};\n\nexport type CreateReviewFigmaReleaseSnapshotOptions =\n CreateReviewFigmaImagesSnapshotOptions & {\n images: readonly ReviewFigmaImage[];\n projectId: string;\n releaseId?: string;\n label?: string;\n createdAt?: string;\n };\n\nexport type CollectReviewFigmaReleaseSnapshotOptions = Omit<\n CreateReviewFigmaReleaseSnapshotOptions,\n 'images'\n> & {\n store: ReviewFigmaImageStore;\n targets: readonly ReviewFigmaImageTarget[];\n};\n\nexport function createReviewFigmaImagesSnapshot(\n images: readonly ReviewFigmaImage[],\n options: CreateReviewFigmaImagesSnapshotOptions = {}\n): ReviewFigmaImagesSnapshot {\n const targetKeys = options.targets?.length\n ? new Set(options.targets.map(getReviewFigmaImageTargetKey))\n : null;\n\n return images\n .filter((image) => {\n if (options.projectId && image.projectId !== options.projectId) {\n return false;\n }\n if (\n targetKeys &&\n !targetKeys.has(getReviewFigmaImageTargetKey(image.target))\n ) {\n return false;\n }\n return true;\n })\n .map(cloneReviewFigmaImage)\n .sort(compareReviewFigmaSnapshotImages);\n}\n\nexport function createReviewFigmaReleaseSnapshot({\n images,\n projectId,\n releaseId,\n label,\n createdAt,\n targets,\n}: CreateReviewFigmaReleaseSnapshotOptions): ReviewFigmaReleaseSnapshot {\n return {\n version: 1,\n projectId,\n ...(releaseId ? { releaseId } : null),\n ...(label ? { label } : null),\n createdAt: createdAt ?? new Date().toISOString(),\n figmaImagesSnapshot: createReviewFigmaImagesSnapshot(images, {\n projectId,\n targets,\n }),\n };\n}\n\nexport async function collectReviewFigmaReleaseSnapshot({\n store,\n targets,\n ...snapshotOptions\n}: CollectReviewFigmaReleaseSnapshotOptions): Promise<ReviewFigmaReleaseSnapshot> {\n const imagesByTarget = await Promise.all(\n targets.map((target) => store.listImages(target))\n );\n\n return createReviewFigmaReleaseSnapshot({\n ...snapshotOptions,\n targets,\n images: dedupeReviewFigmaImages(imagesByTarget.flat()),\n });\n}\n\nfunction dedupeReviewFigmaImages(images: readonly ReviewFigmaImage[]) {\n return Array.from(new Map(images.map((image) => [image.id, image])).values());\n}\n\nfunction cloneReviewFigmaImage(image: ReviewFigmaImage): ReviewFigmaImage {\n return {\n ...image,\n target: cloneReviewFigmaImageTarget(image.target),\n };\n}\n\nfunction cloneReviewFigmaImageTarget(\n target: ReviewFigmaImageTarget\n): ReviewFigmaImageTarget {\n if (target.type === 'figma-node') {\n return {\n type: target.type,\n projectId: target.projectId,\n fileKey: target.fileKey,\n nodeId: target.nodeId,\n };\n }\n\n return {\n type: target.type,\n projectId: target.projectId,\n pageUrl: target.pageUrl,\n slot: target.slot,\n viewport: target.viewport\n ? {\n label: target.viewport.label,\n width: target.viewport.width,\n height: target.viewport.height,\n scope: target.viewport.scope,\n }\n : undefined,\n };\n}\n\nfunction compareReviewFigmaSnapshotImages(\n a: ReviewFigmaImage,\n b: ReviewFigmaImage\n) {\n return (\n a.projectId.localeCompare(b.projectId) ||\n getReviewFigmaImageTargetKey(a.target).localeCompare(\n getReviewFigmaImageTargetKey(b.target)\n ) ||\n a.order - b.order ||\n a.createdAt.localeCompare(b.createdAt) ||\n a.id.localeCompare(b.id)\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
DEFAULT_REVIEW_FIGMA_IMAGE_FORMAT: () => DEFAULT_REVIEW_FIGMA_IMAGE_FORMAT,
|
|
24
24
|
DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT: () => DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT,
|
|
25
|
+
DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE: () => DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE,
|
|
25
26
|
DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY: () => DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,
|
|
26
27
|
DEFAULT_REVIEW_VIEWPORTS: () => DEFAULT_REVIEW_VIEWPORTS,
|
|
27
28
|
FIGMA_NODE_REF_SEPARATOR: () => FIGMA_NODE_REF_SEPARATOR,
|
|
@@ -29,6 +30,7 @@ __export(index_exports, {
|
|
|
29
30
|
REVIEW_WORKFLOW_STATUS_OPTIONS: () => REVIEW_WORKFLOW_STATUS_OPTIONS,
|
|
30
31
|
ReviewFigmaTokenError: () => ReviewFigmaTokenError,
|
|
31
32
|
collectReviewFigmaReleaseSnapshot: () => collectReviewFigmaReleaseSnapshot,
|
|
33
|
+
createRemoteReviewFigmaImageStore: () => createRemoteReviewFigmaImageStore,
|
|
32
34
|
createReviewFigmaFrameUrl: () => createReviewFigmaFrameUrl,
|
|
33
35
|
createReviewFigmaImageStoreClient: () => createReviewFigmaImageStoreClient,
|
|
34
36
|
createReviewFigmaImagesSnapshot: () => createReviewFigmaImagesSnapshot,
|
|
@@ -549,7 +551,10 @@ function createReviewFigmaImageStoreClient(options = {}) {
|
|
|
549
551
|
);
|
|
550
552
|
return request(endpoint, {
|
|
551
553
|
method: "POST",
|
|
552
|
-
body: JSON.stringify(nextInput)
|
|
554
|
+
body: JSON.stringify(nextInput),
|
|
555
|
+
figmaToken: readReviewFigmaImageToken(
|
|
556
|
+
options.token ?? getStoredReviewFigmaImageToken
|
|
557
|
+
)
|
|
553
558
|
});
|
|
554
559
|
},
|
|
555
560
|
updateImage(id, patch) {
|
|
@@ -577,10 +582,12 @@ async function createClientRenderedAddImageInput(input, clientRender, fetchOptio
|
|
|
577
582
|
const token = readClientRenderToken(options);
|
|
578
583
|
if (!token) return input;
|
|
579
584
|
try {
|
|
580
|
-
const asset = await
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
585
|
+
const asset = await createReviewFigmaClientRenderedAsset({
|
|
586
|
+
...options,
|
|
587
|
+
fetch: fetchOption,
|
|
588
|
+
figmaUrl: input.figmaUrl,
|
|
589
|
+
token
|
|
590
|
+
});
|
|
584
591
|
return {
|
|
585
592
|
...input,
|
|
586
593
|
imageFormat: asset.imageFormat,
|
|
@@ -652,6 +659,17 @@ async function createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOpt
|
|
|
652
659
|
height: dimensions.height
|
|
653
660
|
};
|
|
654
661
|
}
|
|
662
|
+
function createReviewFigmaClientRenderedAsset({
|
|
663
|
+
fetch: fetchOption,
|
|
664
|
+
figmaUrl,
|
|
665
|
+
token,
|
|
666
|
+
...options
|
|
667
|
+
}) {
|
|
668
|
+
return withTimeout(
|
|
669
|
+
createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOption),
|
|
670
|
+
options.timeoutMs ?? 1e4
|
|
671
|
+
);
|
|
672
|
+
}
|
|
655
673
|
async function readImageBlobDimensions(blob) {
|
|
656
674
|
const image = await loadImageBlob(blob);
|
|
657
675
|
return {
|
|
@@ -742,11 +760,14 @@ function createReviewFigmaImageStoreRequest(endpoint, fetchOption) {
|
|
|
742
760
|
return async (input, init = {}) => {
|
|
743
761
|
const requestFetch = fetchOption ?? globalThis.fetch;
|
|
744
762
|
if (!requestFetch) throw new Error("Figma image store requires fetch.");
|
|
763
|
+
const figmaToken = init.figmaToken ?? "";
|
|
764
|
+
const { figmaToken: _figmaToken, ...requestInit } = init;
|
|
745
765
|
const response = await requestFetch(input, {
|
|
746
|
-
...
|
|
766
|
+
...requestInit,
|
|
747
767
|
headers: {
|
|
748
768
|
"Content-Type": "application/json",
|
|
749
|
-
...
|
|
769
|
+
...figmaToken ? { "X-Figma-Token": figmaToken } : {},
|
|
770
|
+
...requestInit.headers ?? {}
|
|
750
771
|
}
|
|
751
772
|
});
|
|
752
773
|
const text = await response.text();
|
|
@@ -758,6 +779,18 @@ function createReviewFigmaImageStoreRequest(endpoint, fetchOption) {
|
|
|
758
779
|
return body;
|
|
759
780
|
};
|
|
760
781
|
}
|
|
782
|
+
function readReviewFigmaImageToken(provider) {
|
|
783
|
+
const token = typeof provider === "function" ? provider() : provider;
|
|
784
|
+
return typeof token === "string" ? token.trim() : "";
|
|
785
|
+
}
|
|
786
|
+
function getStoredReviewFigmaImageToken() {
|
|
787
|
+
if (typeof window === "undefined") return "";
|
|
788
|
+
try {
|
|
789
|
+
return window.localStorage.getItem("figma-token") ?? "";
|
|
790
|
+
} catch {
|
|
791
|
+
return "";
|
|
792
|
+
}
|
|
793
|
+
}
|
|
761
794
|
function normalizeReviewFigmaImageTarget(target) {
|
|
762
795
|
if (target.type === "figma-node") {
|
|
763
796
|
return {
|
|
@@ -781,6 +814,300 @@ function normalizeReviewFigmaImageTarget(target) {
|
|
|
781
814
|
};
|
|
782
815
|
}
|
|
783
816
|
|
|
817
|
+
// src/figma/remote.image.store.ts
|
|
818
|
+
var DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE = "review_figma_images";
|
|
819
|
+
var DEFAULT_REVIEW_FIGMA_REMOTE_UPLOAD_TIMEOUT_MS = 3e4;
|
|
820
|
+
var FIGMA_TOKEN_STORAGE_KEY = "figma-token";
|
|
821
|
+
function createRemoteReviewFigmaImageStore({
|
|
822
|
+
client,
|
|
823
|
+
uploadEndpoint,
|
|
824
|
+
table = DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE,
|
|
825
|
+
fetch: fetchOption,
|
|
826
|
+
token,
|
|
827
|
+
clientRender = true,
|
|
828
|
+
uploadTimeoutMs = DEFAULT_REVIEW_FIGMA_REMOTE_UPLOAD_TIMEOUT_MS
|
|
829
|
+
}) {
|
|
830
|
+
const listImages = async (target) => {
|
|
831
|
+
const targetKey = getReviewFigmaImageTargetKey(target);
|
|
832
|
+
const { data, error } = await client.from(table).select("*").eq("project_id", target.projectId).eq("target_key", targetKey).order("sort_order", { ascending: true }).order("created_at", { ascending: true });
|
|
833
|
+
if (error) throwRemoteStoreError(error, "list Figma images");
|
|
834
|
+
return (data ?? []).map(rowToFigmaImage);
|
|
835
|
+
};
|
|
836
|
+
return {
|
|
837
|
+
listImages,
|
|
838
|
+
async addImage(input) {
|
|
839
|
+
const ref = parseReviewFigmaNodeRef(input.figmaUrl);
|
|
840
|
+
if (!ref) {
|
|
841
|
+
throw new Error("A Figma node link or fileKey->nodeId value is required.");
|
|
842
|
+
}
|
|
843
|
+
const id = createFigmaImageId();
|
|
844
|
+
const asset = await resolveFigmaImageAsset({
|
|
845
|
+
input,
|
|
846
|
+
token,
|
|
847
|
+
clientRender,
|
|
848
|
+
fetch: fetchOption
|
|
849
|
+
});
|
|
850
|
+
const uploaded = await uploadFigmaAsset({
|
|
851
|
+
asset,
|
|
852
|
+
endpoint: uploadEndpoint,
|
|
853
|
+
fetch: fetchOption,
|
|
854
|
+
imageId: id,
|
|
855
|
+
projectId: input.target.projectId,
|
|
856
|
+
timeoutMs: uploadTimeoutMs
|
|
857
|
+
});
|
|
858
|
+
const order = typeof input.order === "number" && Number.isFinite(input.order) ? input.order : await getNextImageOrder(listImages, input.target);
|
|
859
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
860
|
+
const row = createImageRow({
|
|
861
|
+
asset,
|
|
862
|
+
fileKey: ref.fileKey,
|
|
863
|
+
id,
|
|
864
|
+
imageUrl: uploaded.imageUrl,
|
|
865
|
+
input,
|
|
866
|
+
nodeId: ref.nodeId,
|
|
867
|
+
now,
|
|
868
|
+
order,
|
|
869
|
+
storageKey: uploaded.storageKey
|
|
870
|
+
});
|
|
871
|
+
const { data, error } = await client.from(table).insert(row).select("*").single();
|
|
872
|
+
if (error) throwRemoteStoreError(error, "insert Figma image");
|
|
873
|
+
return rowToFigmaImage(data);
|
|
874
|
+
},
|
|
875
|
+
async updateImage(id, patch) {
|
|
876
|
+
const { data, error } = await client.from(table).update(createUpdatePatch(patch)).eq("id", id).select("*").single();
|
|
877
|
+
if (error) throwRemoteStoreError(error, "update Figma image");
|
|
878
|
+
return rowToFigmaImage(data);
|
|
879
|
+
},
|
|
880
|
+
async reorderImages(input) {
|
|
881
|
+
const current = await listImages(input.target);
|
|
882
|
+
const currentIds = new Set(current.map((image) => image.id));
|
|
883
|
+
const nextIds = [
|
|
884
|
+
...input.imageIds.filter((id) => currentIds.has(id)),
|
|
885
|
+
...current.map((image) => image.id).filter((id) => !input.imageIds.includes(id))
|
|
886
|
+
];
|
|
887
|
+
const targetKey = getReviewFigmaImageTargetKey(input.target);
|
|
888
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
889
|
+
const results = await Promise.all(
|
|
890
|
+
nextIds.map(
|
|
891
|
+
(id, index) => client.from(table).update({ sort_order: index, updated_at: updatedAt }).eq("id", id).eq("project_id", input.target.projectId).eq("target_key", targetKey)
|
|
892
|
+
)
|
|
893
|
+
);
|
|
894
|
+
const failed = results.find((result) => result.error);
|
|
895
|
+
if (failed?.error) throwRemoteStoreError(failed.error, "reorder Figma images");
|
|
896
|
+
return listImages(input.target);
|
|
897
|
+
},
|
|
898
|
+
async deleteImage(id) {
|
|
899
|
+
const { error } = await client.from(table).delete().eq("id", id);
|
|
900
|
+
if (error) throwRemoteStoreError(error, "delete Figma image");
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
async function resolveFigmaImageAsset({
|
|
905
|
+
input,
|
|
906
|
+
token,
|
|
907
|
+
clientRender,
|
|
908
|
+
fetch
|
|
909
|
+
}) {
|
|
910
|
+
if (input.asset) return input.asset;
|
|
911
|
+
if (!clientRender) {
|
|
912
|
+
throw new Error("Figma image asset is required when clientRender is disabled.");
|
|
913
|
+
}
|
|
914
|
+
const renderOptions = clientRender === true ? {} : clientRender;
|
|
915
|
+
const figmaToken = readRemoteFigmaToken(renderOptions.token ?? token);
|
|
916
|
+
if (!figmaToken) throw new Error("Figma token is required.");
|
|
917
|
+
return createReviewFigmaClientRenderedAsset({
|
|
918
|
+
...renderOptions,
|
|
919
|
+
fetch,
|
|
920
|
+
figmaUrl: input.figmaUrl,
|
|
921
|
+
token: figmaToken
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
async function uploadFigmaAsset({
|
|
925
|
+
asset,
|
|
926
|
+
endpoint,
|
|
927
|
+
fetch: fetchOption,
|
|
928
|
+
imageId,
|
|
929
|
+
projectId,
|
|
930
|
+
timeoutMs
|
|
931
|
+
}) {
|
|
932
|
+
const requestFetch = fetchOption ?? globalThis.fetch;
|
|
933
|
+
if (!requestFetch) throw new Error("Figma asset upload requires fetch.");
|
|
934
|
+
const { blob, mimeType } = decodeAssetDataUrl(asset);
|
|
935
|
+
const response = await withTimeout2(
|
|
936
|
+
requestFetch(
|
|
937
|
+
createAssetUploadUrl(endpoint, {
|
|
938
|
+
imageFormat: asset.imageFormat,
|
|
939
|
+
imageId,
|
|
940
|
+
projectId
|
|
941
|
+
}),
|
|
942
|
+
{
|
|
943
|
+
method: "POST",
|
|
944
|
+
headers: {
|
|
945
|
+
"Content-Type": mimeType
|
|
946
|
+
},
|
|
947
|
+
body: blob
|
|
948
|
+
}
|
|
949
|
+
),
|
|
950
|
+
timeoutMs,
|
|
951
|
+
"Figma asset upload timed out."
|
|
952
|
+
);
|
|
953
|
+
const data = await response.json().catch(() => null);
|
|
954
|
+
if (!response.ok) {
|
|
955
|
+
throw new Error(
|
|
956
|
+
readString(data?.error) || `Figma asset upload failed with ${response.status}`
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
const storageKey = readString(data?.storageKey) ?? readString(data?.r2Key);
|
|
960
|
+
const imageUrl = readString(data?.imageUrl) ?? readString(data?.publicUrl);
|
|
961
|
+
if (!storageKey || !imageUrl) {
|
|
962
|
+
throw new Error("Figma asset upload response is missing image URL.");
|
|
963
|
+
}
|
|
964
|
+
return { storageKey, imageUrl };
|
|
965
|
+
}
|
|
966
|
+
function createAssetUploadUrl(endpoint, params) {
|
|
967
|
+
const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
968
|
+
const url = new URL(endpoint, base);
|
|
969
|
+
url.searchParams.set("projectId", params.projectId);
|
|
970
|
+
url.searchParams.set("imageId", params.imageId);
|
|
971
|
+
url.searchParams.set("imageFormat", params.imageFormat);
|
|
972
|
+
return url.toString();
|
|
973
|
+
}
|
|
974
|
+
function createImageRow({
|
|
975
|
+
asset,
|
|
976
|
+
fileKey,
|
|
977
|
+
id,
|
|
978
|
+
imageUrl,
|
|
979
|
+
input,
|
|
980
|
+
nodeId,
|
|
981
|
+
now,
|
|
982
|
+
order,
|
|
983
|
+
storageKey
|
|
984
|
+
}) {
|
|
985
|
+
const target = input.target;
|
|
986
|
+
const targetKey = getReviewFigmaImageTargetKey(target);
|
|
987
|
+
return {
|
|
988
|
+
id,
|
|
989
|
+
project_id: target.projectId,
|
|
990
|
+
target_key: targetKey,
|
|
991
|
+
target,
|
|
992
|
+
target_type: target.type,
|
|
993
|
+
page_url: target.type === "route" ? target.pageUrl : null,
|
|
994
|
+
viewport_label: target.type === "route" ? target.viewport?.label ?? null : null,
|
|
995
|
+
viewport_width: target.type === "route" ? target.viewport?.width ?? null : null,
|
|
996
|
+
viewport_height: target.type === "route" ? target.viewport?.height ?? null : null,
|
|
997
|
+
viewport_scope: target.type === "route" ? target.viewport?.scope ?? null : null,
|
|
998
|
+
slot: target.type === "route" ? target.slot ?? null : null,
|
|
999
|
+
figma_url: input.figmaUrl,
|
|
1000
|
+
file_key: fileKey,
|
|
1001
|
+
node_id: nodeId,
|
|
1002
|
+
image_url: imageUrl,
|
|
1003
|
+
image_format: asset.imageFormat,
|
|
1004
|
+
mime_type: asset.mimeType,
|
|
1005
|
+
storage_key: storageKey,
|
|
1006
|
+
label: normalizeOptionalText(input.label) ?? null,
|
|
1007
|
+
sort_order: order,
|
|
1008
|
+
width: asset.width ?? null,
|
|
1009
|
+
height: asset.height ?? null,
|
|
1010
|
+
byte_size: asset.byteSize ?? null,
|
|
1011
|
+
created_at: now,
|
|
1012
|
+
updated_at: now
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
function createUpdatePatch(patch) {
|
|
1016
|
+
return {
|
|
1017
|
+
...patch.label !== void 0 ? { label: normalizeOptionalText(patch.label) ?? null } : {},
|
|
1018
|
+
...typeof patch.order === "number" && Number.isFinite(patch.order) ? { sort_order: patch.order } : {},
|
|
1019
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
async function getNextImageOrder(listImages, target) {
|
|
1023
|
+
const images = await listImages(target);
|
|
1024
|
+
return images.length ? Math.max(...images.map((image) => image.order)) + 1 : 0;
|
|
1025
|
+
}
|
|
1026
|
+
function rowToFigmaImage(row) {
|
|
1027
|
+
return {
|
|
1028
|
+
id: row.id,
|
|
1029
|
+
projectId: row.project_id,
|
|
1030
|
+
target: row.target,
|
|
1031
|
+
figmaUrl: row.figma_url,
|
|
1032
|
+
fileKey: row.file_key,
|
|
1033
|
+
nodeId: row.node_id,
|
|
1034
|
+
imageUrl: row.image_url,
|
|
1035
|
+
imageFormat: row.image_format,
|
|
1036
|
+
mimeType: row.mime_type,
|
|
1037
|
+
label: row.label ?? void 0,
|
|
1038
|
+
order: row.sort_order,
|
|
1039
|
+
storageKey: row.storage_key,
|
|
1040
|
+
width: row.width ?? void 0,
|
|
1041
|
+
height: row.height ?? void 0,
|
|
1042
|
+
byteSize: row.byte_size ?? void 0,
|
|
1043
|
+
createdAt: row.created_at,
|
|
1044
|
+
updatedAt: row.updated_at
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function decodeAssetDataUrl(asset) {
|
|
1048
|
+
const match = asset.dataUrl.match(/^data:([^;,]+);base64,(.*)$/);
|
|
1049
|
+
if (!match) throw new Error("Valid Figma image asset data URL is required.");
|
|
1050
|
+
const mimeType = match[1]?.trim();
|
|
1051
|
+
if (!mimeType || mimeType !== asset.mimeType) {
|
|
1052
|
+
throw new Error("Figma image asset MIME type mismatch.");
|
|
1053
|
+
}
|
|
1054
|
+
const binary = globalThis.atob(match[2] ?? "");
|
|
1055
|
+
const bytes = new Uint8Array(binary.length);
|
|
1056
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
1057
|
+
bytes[index] = binary.charCodeAt(index);
|
|
1058
|
+
}
|
|
1059
|
+
return {
|
|
1060
|
+
blob: new Blob([bytes], { type: mimeType }),
|
|
1061
|
+
mimeType
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
async function withTimeout2(promise, timeoutMs, message) {
|
|
1065
|
+
let timeoutId;
|
|
1066
|
+
try {
|
|
1067
|
+
return await Promise.race([
|
|
1068
|
+
promise,
|
|
1069
|
+
new Promise((_, reject) => {
|
|
1070
|
+
timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
1071
|
+
})
|
|
1072
|
+
]);
|
|
1073
|
+
} finally {
|
|
1074
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
function readRemoteFigmaToken(provider) {
|
|
1078
|
+
const token = typeof provider === "function" ? provider() : provider;
|
|
1079
|
+
if (typeof token === "string" && token.trim()) return token.trim();
|
|
1080
|
+
return readStoredFigmaToken();
|
|
1081
|
+
}
|
|
1082
|
+
function readStoredFigmaToken() {
|
|
1083
|
+
if (typeof window === "undefined") return "";
|
|
1084
|
+
try {
|
|
1085
|
+
return window.localStorage.getItem(FIGMA_TOKEN_STORAGE_KEY)?.trim() || "";
|
|
1086
|
+
} catch {
|
|
1087
|
+
return "";
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
function createFigmaImageId() {
|
|
1091
|
+
const id = globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2);
|
|
1092
|
+
return `figma_${id.replace(/[^A-Za-z0-9_-]+/g, "-")}`;
|
|
1093
|
+
}
|
|
1094
|
+
function normalizeOptionalText(value) {
|
|
1095
|
+
if (typeof value !== "string") return void 0;
|
|
1096
|
+
return value.trim() || void 0;
|
|
1097
|
+
}
|
|
1098
|
+
function readString(value) {
|
|
1099
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1100
|
+
}
|
|
1101
|
+
function throwRemoteStoreError(error, action) {
|
|
1102
|
+
const source = error;
|
|
1103
|
+
const message = readString(source.message) ?? `Remote Figma image ${action} failed.`;
|
|
1104
|
+
const code = readString(source.code);
|
|
1105
|
+
const details = readString(source.details);
|
|
1106
|
+
const hint = readString(source.hint);
|
|
1107
|
+
const suffix = [code, details, hint].filter(Boolean).join(" / ");
|
|
1108
|
+
throw new Error(suffix ? `${message} (${suffix})` : message);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
784
1111
|
// src/figma/image.snapshot.ts
|
|
785
1112
|
function createReviewFigmaImagesSnapshot(images, options = {}) {
|
|
786
1113
|
const targetKeys = options.targets?.length ? new Set(options.targets.map(getReviewFigmaImageTargetKey)) : null;
|
|
@@ -5363,6 +5690,7 @@ function createNoopController() {
|
|
|
5363
5690
|
0 && (module.exports = {
|
|
5364
5691
|
DEFAULT_REVIEW_FIGMA_IMAGE_FORMAT,
|
|
5365
5692
|
DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT,
|
|
5693
|
+
DEFAULT_REVIEW_FIGMA_REMOTE_IMAGES_TABLE,
|
|
5366
5694
|
DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,
|
|
5367
5695
|
DEFAULT_REVIEW_VIEWPORTS,
|
|
5368
5696
|
FIGMA_NODE_REF_SEPARATOR,
|
|
@@ -5370,6 +5698,7 @@ function createNoopController() {
|
|
|
5370
5698
|
REVIEW_WORKFLOW_STATUS_OPTIONS,
|
|
5371
5699
|
ReviewFigmaTokenError,
|
|
5372
5700
|
collectReviewFigmaReleaseSnapshot,
|
|
5701
|
+
createRemoteReviewFigmaImageStore,
|
|
5373
5702
|
createReviewFigmaFrameUrl,
|
|
5374
5703
|
createReviewFigmaImageStoreClient,
|
|
5375
5704
|
createReviewFigmaImagesSnapshot,
|