@designfever/web-review-kit 0.7.0 → 0.7.1

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.
@@ -63,6 +63,7 @@ import {
63
63
 
64
64
  const REVIEW_PROJECT_ID = import.meta.env.VITE_REVIEW_PROJECT_ID || 'my-project';
65
65
  const REVIEW_PATH_PREFIX = '/review';
66
+ const REVIEW_USER_ID = import.meta.env.VITE_REVIEW_USER_ID || '';
66
67
 
67
68
  const local = localAdapter({
68
69
  storageKey: `${REVIEW_PROJECT_ID}-review-items`,
@@ -155,6 +156,7 @@ const remote = supabaseClient
155
156
  const adapters = [
156
157
  {
157
158
  label: 'local',
159
+ defaultUserId: REVIEW_USER_ID,
158
160
  get: (id) => local.get(id),
159
161
  list: (query) => local.list(query),
160
162
  create: (item) => local.create(item),
@@ -168,6 +170,7 @@ const adapters = [
168
170
  ? [
169
171
  {
170
172
  label: 'supabase',
173
+ defaultUserId: REVIEW_USER_ID,
171
174
  get: (id) => remote.get(id),
172
175
  list: (query) => remote.list(query),
173
176
  create: (item) => remote.create(item),
@@ -11,6 +11,7 @@
11
11
  - Figma image local cache와 Vite plugin helper를 추가했다.
12
12
  - QA 작성 필드를 `fields` option으로 확장해 title을 선택적으로 받을 수 있게 했다.
13
13
  - QA item에 optional assignee 표시/변경 flow를 추가했다.
14
+ - adapter `defaultUserId`로 Settings `user-id` 기본값을 줄 수 있게 했다.
14
15
  - 느린 remote adapter, 특히 df-sheet 연동에서 list/create/update/delete pending 상태를 UI로 표시한다.
15
16
  - deep link item restore와 QA list focus 위치 보정을 개선했다.
16
17
  - right rail Figma icon stroke를 다른 rail icon과 분리해 맞췄다.
@@ -28,7 +29,8 @@
28
29
  review shell에서 Figma reference image를 등록하고 target page 위에 overlay로 올릴 수 있게 했다.
29
30
 
30
31
  - dev server plugin이 Figma API 호출과 local asset cache를 담당한다.
31
- - Figma token은 server env의 `FIGMA_TOKEN`을 사용하고 browser bundle로 노출하지 않는다.
32
+ - Figma token은 server env의 `FIGMA_TOKEN`을 우선 사용하고, 없으면
33
+ Settings의 browser-local `figma-token`을 image store 요청 fallback으로 사용한다.
32
34
  - metadata는 `.df-review/figma-images.json`, asset은 `.df-review/figma-assets/`에 저장한다.
33
35
  - review shell에서는 image manager panel과 layer controls를 제공한다.
34
36
  - 일반 route에서는 host가 연결한 overlay control만 사용할 수 있게 boundary를 분리했다.
@@ -40,6 +42,7 @@ adapter option에 `fields`를 추가했다.
40
42
  - `fields.title`이 true일 때만 QA 작성/수정 화면에 title input을 표시한다.
41
43
  - title은 optional field이며 없으면 기존 comment-only 작성 흐름을 유지한다.
42
44
  - local fallback에는 assignee sample을 넣지 않고 host adapter가 필요한 field만 선언한다.
45
+ - `defaultUserId`는 Settings localStorage `user-id`가 비어 있을 때 기본 reviewer id로 사용한다.
43
46
 
44
47
  ### Assignee
45
48
 
@@ -92,7 +95,8 @@ remote list가 늦게 도착하는 경우에도 URL의 `item` query를 복원할
92
95
  Figma image local cache를 쓰는 host는 Vite config에 review-kit plugin helper를 연결해야 한다.
93
96
 
94
97
  - `reviewFigmaImageStore()`는 dev/local cache endpoint를 제공한다.
95
- - `FIGMA_TOKEN`은 server env로만 둔다.
98
+ - 개발팀 공용 token은 `FIGMA_TOKEN` server env 둔다.
99
+ - Vercel 등 검수 환경에서 개인 token이 필요하면 Settings의 Figma token을 사용한다.
96
100
  - image format 변환이 필요하면 host가 `sharp` 같은 변환기를 plugin option으로 연결한다.
97
101
 
98
102
  ## 문서
@@ -0,0 +1,34 @@
1
+ # 릴리즈 노트: 0.7.1
2
+
3
+ 0.7.0 release 이후 fallback 설정을 보강한 patch release.
4
+
5
+ 비교 기준: `0.7.0`
6
+ 검토 기준: `main`
7
+
8
+ ## 변경
9
+
10
+ - Figma image create 요청에서 server env `FIGMA_TOKEN`을 우선 사용하고, 없으면 Settings의 browser-local `figma-token`을 fallback으로 사용한다.
11
+ - list/update/delete 요청에는 Figma token header를 보내지 않고, Figma API가 필요한 create path에서만 token을 전달한다.
12
+ - adapter `defaultUserId`로 Settings `user-id` 기본값을 줄 수 있게 했다.
13
+ - Settings localStorage `user-id`가 있으면 저장값을 우선 사용하고, 비어 있을 때만 adapter 기본값을 사용한다.
14
+ - dev adapter sample과 installation guide에 `VITE_REVIEW_USER_ID` 예시를 추가했다.
15
+ - 페이지 전환 후 Source Tree가 빈 상태로 남지 않도록 iframe load/DOM mutation 이후 component outline을 다시 읽는다.
16
+
17
+ ## 검증
18
+
19
+ - `pnpm run typecheck`
20
+ - `pnpm run typecheck:dev`
21
+ - `pnpm run lint:dead-code`
22
+ - `pnpm run build`
23
+ - `npm pack --dry-run --json`
24
+
25
+ 수동 확인:
26
+
27
+ - Settings modal에서 `VITE_REVIEW_USER_ID=env-reviewer`가 Review user ID 기본값으로 표시되는지 확인했다.
28
+ - About help text에서 Figma token fallback 안내가 표시되는지 확인했다.
29
+ - fake fetch로 Figma token header가 addImage create 요청에만 붙는지 확인했다.
30
+ - review shell에서 Source Tree를 연 뒤 `/components/`로 이동해 tree가 토글 없이 현재 DOM 기준으로 유지되는지 확인했다.
31
+
32
+ ## 메모
33
+
34
+ - npm `0.7.0`은 `070e2e2` 기준으로 이미 publish되어, 이 patch는 `0.7.1`로 배포한다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@designfever/web-review-kit",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "description": "Designfever web page review overlay toolkit.",
6
6
  "license": "Apache-2.0",
@@ -1 +0,0 @@
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\nexport type ReviewFigmaImageStoreClientOptions = {\n endpoint?: string;\n fetch?: typeof fetch;\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 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 });\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 withTimeout(\n createClientRenderedFigmaAsset(input.figmaUrl, token, options, fetchOption),\n options.timeoutMs ?? 10000\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\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\nfunction createReviewFigmaImageStoreRequest(\n endpoint: string,\n fetchOption: typeof fetch | undefined\n) {\n return async <T>(input: string, init: RequestInit = {}) => {\n const requestFetch = fetchOption ?? globalThis.fetch;\n if (!requestFetch) throw new Error('Figma image store requires fetch.');\n\n const response = await requestFetch(input, {\n ...init,\n headers: {\n 'Content-Type': 'application/json',\n ...(init.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 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":[]}