{"record":{"id":"fa0e59b679577900","repo":"toeverything/AFFiNE","slug":"invalid-link-preview-response","errorCode":null,"errorMessage":"Invalid link preview response","messagePattern":"Invalid link preview response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"blocksuite/affine/shared/src/services/link-preview-service/response.ts","lineNumber":112,"sourceCode":"  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();\n  }\n  const data = parseLinkPreviewResponse(JSON.parse(json));\n  if (!data) throw new Error('Invalid link preview response');\n  return data;\n}\n","sourceCodeStart":94,"sourceCodeEnd":115,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/blocksuite/affine/shared/src/services/link-preview-service/response.ts#L94-L115","documentation":"After streaming the body, readLinkPreviewResponse JSON-parses it and validates against LinkPreviewResponseSchema via parseLinkPreviewResponse, which returns undefined on schema failure. If parsing or validation fails, 'Invalid link preview response' is thrown. The schema requires at minimum a valid http/https url without credentials; all other fields are optional or lenient.","triggerScenarios":"The response body is not valid JSON (HTML error page, truncated body) so JSON.parse throws, or the parsed object fails the zod schema — most commonly missing or invalid 'url' field, a non-http(s) URL, or a URL containing username/password credentials.","commonSituations":"The preview backend returned an HTML error page with a 200 status; the backend API changed shape (version drift); the response was truncated by an intermediary proxy; the 'url' field uses ftp: or includes embedded credentials.","solutions":["Log the raw response body and validate it against the schema requirements (must be JSON with a valid http/https 'url')","Confirm the preview backend returns the documented schema and hasn't changed its API shape","Check for proxies/CDNs intercepting and returning HTML (login pages, error pages) with 200 status","Catch the error and fall back to rendering a plain link without preview data"],"exampleFix":"// before\nconst data = await readLinkPreviewResponse(res);\n// after\ntry {\n  const data = await readLinkPreviewResponse(res);\n} catch (e) {\n  if (e.message === 'Invalid link preview response') {\n    const raw = await res.clone().text(); // inspect what the server actually sent\n    console.warn('Unexpected preview payload:', raw.slice(0, 200));\n  }\n}","handlingStrategy":"validation","validationCode":"// validate after fetch, before parsing expectations\nconst clone = response.clone();\nconst text = await clone.text();\nlet parsed: unknown;\ntry {\n  parsed = JSON.parse(text);\n} catch {\n  throw new Error('Preview response is not JSON');\n}\nif (\n  typeof parsed !== 'object' || parsed === null ||\n  !('url' in parsed) || typeof (parsed as {url: unknown}).url !== 'string' ||\n  !/^https?:\\/\\/[^@]+@/.test((parsed as {url: string}).url) === false\n) {\n  throw new Error('Preview response missing valid http(s) url');\n}","typeGuard":"function isLinkPreviewResponse(v: unknown): v is { url: string; [k: string]: unknown } {\n  if (typeof v !== 'object' || v === null) return false;\n  const url = (v as { url?: unknown }).url;\n  if (typeof url !== 'string') return false;\n  try {\n    const u = new URL(url);\n    return ['http:', 'https:'].includes(u.protocol) && !u.username && !u.password;\n  } catch {\n    return false;\n  }\n}","tryCatchPattern":"try {\n  const data = await readLinkPreviewResponse(response);\n} catch (e) {\n  if (e.message === 'Invalid link preview response') {\n    // log body snippet and render plain link fallback\n  } else throw e;\n}","preventionTips":["Ensure the preview backend always returns application/json with a valid http(s) 'url' field","Check content-type header is application/json before parsing","Watch for proxies/CDNs returning HTML with 200 status (intercept and detect non-JSON bodies)","Pin and test against the backend API version to catch schema drift early"],"tags":["json","validation","zod","schema","http"],"backgroundTag":"unexpected-response-shape","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"}