{"record":{"id":"44371fa15035b7e8","repo":"jackwener/OpenCLI","slug":"linkedin-messengermessages-discovery-returned-an-i","errorCode":null,"errorMessage":"LinkedIn messengerMessages discovery returned an invalid URL.","messagePattern":"LinkedIn messengerMessages discovery returned an invalid URL\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/thread-snapshot.js","lineNumber":183,"sourceCode":"    let decoded = url;\n    try { decoded = decodeURIComponent(url); } catch {}\n    const match = decoded.match(/conversationUrn:urn:li:msg_conversation:\\((urn:li:fsd_profile:[^,)]+)/i);\n    if (match) return match[1];\n  }\n  return '';\n}\n\nfunction validateThreadApiUrls(apiUrls, threadUrl) {\n  const threadId = new URL(threadUrl).pathname.match(/^\\/messaging\\/thread\\/([^/]+)\\/?$/i)?.[1] || '';\n  if (!Array.isArray(apiUrls) || apiUrls.length === 0) {\n    throw new CommandExecutionError('LinkedIn did not issue a messengerMessages API request for this thread.');\n  }\n  for (const value of apiUrls) {\n    let url;\n    try {\n      url = new URL(value);\n    } catch {\n      throw new CommandExecutionError('LinkedIn messengerMessages discovery returned an invalid URL.');\n    }\n    let decoded = value;\n    try { decoded = decodeURIComponent(value); } catch {}\n    if (url.protocol !== 'https:'\n      || url.hostname !== LINKEDIN_DOMAIN\n      || url.pathname !== '/voyager/api/voyagerMessagingGraphQL/graphql'\n      || !/^messengerMessages\\.[a-f0-9]+$/i.test(url.searchParams.get('queryId') || '')\n      || !threadId\n      || !decoded.includes(threadId)) {\n      throw new CommandExecutionError('LinkedIn messengerMessages discovery returned an unsafe or mismatched URL.');\n    }\n  }\n  return apiUrls;\n}\n\nfunction parseThreadPages(pages) {\n  if (!Array.isArray(pages) || pages.length === 0) {\n    throw new CommandExecutionError('LinkedIn messengerMessages API returned no pages.');","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/thread-snapshot.js#L165-L201","documentation":"Thrown by validateThreadApiUrls when a captured discovery URL cannot be parsed with the URL constructor. The library defensively validates every URL harvested from the browser's performance entries before replaying it, and an unparseable string means the discovery data is corrupt or was tampered with.","triggerScenarios":"An entry in performance resource entries matched the voyager GraphQL regex but produced a string that new URL(value) rejects (malformed, empty after trimming, or containing invalid characters).","commonSituations":"A browser extension or injected script polluted resource timing entries; LinkedIn served a redirect/proxy URL with unencoded characters; truncated URLs from a modified performance buffer; custom instrumentation recording partial URLs.","solutions":["Reload the thread page and rerun the command so discovery captures fresh, well-formed resource entries.","Disable browser extensions that modify network requests or resource timing, then retry.","Inspect the captured values (log apiUrls before validation) and widen/narrow the discovery regex in buildThreadApiDiscoveryScript to match only well-formed URLs."],"exampleFix":"// before: accepting any voyager match\nif (!/\\/voyager\\/api\\/voyagerMessagingGraphQL\\/graphql/i.test(url)) continue;\n// after: require a queryId and parseable shape before pushing\nif (!/\\/[?&]queryId=messengerMessages\\.[a-f0-9]+/i.test(url)) continue;\ntry { new URL(url); } catch { continue; }\napiUrls.push(url);","handlingStrategy":"validation","validationCode":"function validateApiUrls(urls) {\n  if (!Array.isArray(urls)) throw new Error('apiUrls must be an array');\n  for (const u of urls) new URL(u); // throws TypeError on unparseable\n}","typeGuard":"function isParseableUrl(v) {\n  if (typeof v !== 'string') return false;\n  try { new URL(v); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  const snapshot = await cli.linkedin.threadSnapshot({ url: threadUrl });\n} catch (err) {\n  if (err.message.includes('discovery returned an invalid URL')) {\n    // retry once on a freshly reloaded page; else report LinkedIn/extension interference\n  } else throw err;\n}","preventionTips":["Run in a clean browser profile without network-rewriting extensions.","Reload the page before capture so performance entries are fresh.","Never manually construct or edit the discovered URLs — let the discovery script harvest them."],"tags":["linkedin","url-validation","scraping"],"backgroundTag":"invalid-url-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}