{"record":{"id":"a699bef61ac788ff","repo":"koala73/worldmonitor","slug":"invalid-telegram-channel-preview","errorCode":null,"errorMessage":"Invalid Telegram channel preview","messagePattern":"Invalid Telegram channel preview","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/telegram-intel.ts","lineNumber":147,"sourceCode":"  }\n\n  let errorMessage = `${response.status}`;\n  try {\n    const errorJson = await response.json() as { error?: string };\n    errorMessage = errorJson.error || errorMessage;\n  } catch {\n    errorMessage = `${response.status}`;\n  }\n  throw new TelegramLookupError(errorMessage, response.status, parseRetryAfterMs(response));\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n  return value && typeof value === 'object' ? value as Record<string, unknown> : null;\n}\n\nfunction parseTelegramChannelPreview(value: unknown): TelegramChannelPreview {\n  const parsed = asRecord(value);\n  if (!parsed) throw new Error('Invalid Telegram channel preview');\n  const username = normalizeTelegramUsername(String(parsed.username || ''));\n  if (!username) throw new Error('Invalid Telegram channel preview');\n  const memberCount = parsed.memberCount == null ? null : Number(parsed.memberCount);\n  return {\n    username,\n    title: typeof parsed.title === 'string' && parsed.title.trim() ? parsed.title.trim() : username,\n    memberCount: memberCount != null && Number.isFinite(memberCount) && memberCount >= 0\n      ? Math.floor(memberCount)\n      : null,\n    url: `https://t.me/${username}`,\n  };\n}\n\nfunction parseTelegramItem(value: unknown): TelegramItem | null {\n  const parsed = asRecord(value);\n  if (!parsed) return null;\n  const required = ['id', 'channel', 'channelTitle', 'url', 'ts', 'text', 'topic'] as const;\n  for (const key of required) {","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/src/services/telegram-intel.ts#L129-L165","documentation":"parseTelegramChannelPreview validates the shape of a channel-preview payload. If the value is not a plain object (asRecord returns null for non-objects), the payload doesn't match the expected preview schema and the function throws 'Invalid Telegram channel preview'. It guards against edge/upstream responses that silently changed shape.","triggerScenarios":"Calling preview() where the fetch resolves with a body that is not an object — e.g. an HTML error page parsed leniently, a null/undefined body, an array, or an edge response whose JSON is a string or number.","commonSituations":"Edge function returning an error page or empty body with 200; API contract change between client and deployed edge version; caches serving stale/malformed payloads; proxy stripping or rewriting response bodies.","solutions":["Check response.ok and content-type before parsing so HTML/error bodies don't reach the parser.","Log the raw payload when this throws to identify which side (edge vs upstream) broke the contract.","Redeploy/align the edge Telegram preview endpoint with the client's expected TelegramChannelPreview schema.","Wrap preview() in try/catch and show a 'preview unavailable' state instead of crashing the caller."],"exampleFix":"// before\nconst preview = await previewChannel(username); // throws on malformed body\n// after\nlet preview = null;\ntry {\n  preview = await previewChannel(username);\n} catch (e) {\n  console.warn('Telegram preview unavailable', e);\n}\nif (isTelegramChannelPreview(preview)) { render(preview); }","handlingStrategy":"type-guard","validationCode":"// check before trusting the payload\nconst body = await res.json();\nif (body == null || typeof body !== 'object' || Array.isArray(body)) {\n  throw new Error('Unexpected preview payload');\n}\n","typeGuard":"function isTelegramChannelPreview(v: unknown): v is TelegramChannelPreview {\n  if (typeof v !== 'object' || v === null) return false;\n  const r = v as Record<string, unknown>;\n  return typeof r.username === 'string' && r.username.trim().length > 0;\n}\n","tryCatchPattern":"try {\n  const preview = parseTelegramChannelPreview(body);\n} catch {\n  showPreviewUnavailable(); // fall back to disabled preview state\n}\n","preventionTips":["Validate response.ok and content-type: application/json before parsing","Type-guard every external payload at the boundary instead of trusting the wire shape","Pin client and edge deployments so schemas can't drift mid-flight","Log raw bodies on parse failure to speed up contract-break diagnosis"],"tags":["validation","schema-mismatch","telegram","api-response"],"backgroundTag":"schema-validation-failed","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}