{"record":{"id":"c266feb99a74a848","repo":"toeverything/AFFiNE","slug":"link-preview-unavailable","errorCode":null,"errorMessage":"Link preview unavailable","messagePattern":"Link preview unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"blocksuite/affine/shared/src/services/link-preview-service/response.ts","lineNumber":91,"sourceCode":"    .optional()\n    .catch(undefined),\n});\n\nexport type LinkPreviewResponseData = z.infer<typeof LinkPreviewResponseSchema>;\n\nexport function parseLinkPreviewResponse(\n  value: unknown\n): LinkPreviewResponseData | undefined {\n  const result = LinkPreviewResponseSchema.safeParse(value);\n  return result.success ? result.data : undefined;\n}\n\nexport async function readLinkPreviewResponse(\n  response: Response,\n  maxBytes = 4 * 1024 * 1024\n) {\n  if (!response.ok || !response.body)\n    throw new Error('Link preview unavailable');\n  const reader = response.body.getReader();\n  const decoder = new TextDecoder();\n  let bytes = 0;\n  let json = '';\n  try {\n    for (;;) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      bytes += value.byteLength;\n      if (bytes > maxBytes) {\n        await reader.cancel();\n        throw new Error('Link preview response too large');\n      }\n      json += decoder.decode(value, { stream: true });\n    }\n    json += decoder.decode();\n  } finally {\n    reader.releaseLock();","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/blocksuite/affine/shared/src/services/link-preview-service/response.ts#L73-L109","documentation":"readLinkPreviewResponse throws 'Link preview unavailable' when the fetch Response for a link preview is not ok (non-2xx HTTP status) or has no readable body stream. The library requires a successful HTTP response with a body before it will attempt to stream and parse preview JSON. This is a guard against processing error responses or empty bodies from the preview backend.","triggerScenarios":"Calling readLinkPreviewResponse (via the link-preview data()/readShareLinkPreview path) with a Response whose response.ok is false (404, 500, 403, etc.) or whose response.body is null (e.g. opaque/no-cors fetch responses, 204 No Content, or body already consumed).","commonSituations":"The preview backend is down or returns 5xx; the shared link was deleted (404); a fetch made with mode:'no-cors' yields an opaque response with null body; the network proxy returns an error page; the response was consumed elsewhere before being passed in.","solutions":["Check response.ok and response.status before calling readLinkPreviewResponse, and handle non-2xx statuses explicitly","Verify the fetch was not made with mode:'no-cors' (opaque responses have a null body)","Inspect server logs / backend health for the preview endpoint returning non-2xx","Wrap the call in try-catch and degrade gracefully (skip preview) when unavailable"],"exampleFix":"// before\nconst res = await fetch(previewUrl);\nconst data = await readLinkPreviewResponse(res);\n// after\nconst res = await fetch(previewUrl);\nif (!res.ok) {\n  console.warn(`Preview fetch failed: ${res.status}`);\n} else {\n  const data = await readLinkPreviewResponse(res);\n}","handlingStrategy":"try-catch","validationCode":"// before calling\nif (!response.ok || !response.body) {\n  throw new Error(`Preview unavailable: HTTP ${response.status}`);\n}","typeGuard":"function hasReadableBody(res: Response): res is Response & { body: ReadableStream } {\n  return res.ok && res.body !== null;\n}","tryCatchPattern":"try {\n  const data = await readLinkPreviewResponse(response);\n} catch (e) {\n  if (e.message === 'Link preview unavailable') {\n    // degrade: render plain link instead of preview card\n  } else throw e;\n}","preventionTips":["Always check response.ok and response.status before consuming a preview response","Avoid fetch mode:'no-cors' for preview requests (opaque responses have null body)","Do not consume response.body before passing the Response to readLinkPreviewResponse","Monitor the preview endpoint's availability and status codes in production"],"tags":["http","network","fetch","response"],"backgroundTag":"http-error-response","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-09-15T22:21:42.632Z","contentChangedAt":"2026-09-15T22:21:42.632Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}