{"record":{"id":"53f8a5b4c27493c3","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-cursor","errorCode":"error-invalid-cursor","errorMessage":"error-invalid-cursor","messagePattern":"error-invalid-cursor","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/messages/loadRoomHistory.ts","lineNumber":30,"sourceCode":"export type RoomHistoryCursor = {\n\tnext: string | null;\n\tprevious: string | null;\n};\n\nexport type RoomHistoryResult = {\n\tmessages: IMessage[];\n\tcursor: RoomHistoryCursor;\n\tfirstUnread?: IMessage;\n\tunreadNotLoaded?: number;\n};\n\nexport function encodeHistoryCursor(ts: Date): string {\n\treturn `${ts.getTime()}`;\n}\n\nexport function decodeHistoryCursor(cursor: string): Date {\n\tif (!/^\\d+$/.test(cursor)) {\n\t\tthrow new Error('error-invalid-cursor');\n\t}\n\n\tconst date = new Date(parseInt(cursor, 10));\n\n\tif (date.toString() === 'Invalid Date') {\n\t\tthrow new Error('error-invalid-cursor');\n\t}\n\n\treturn date;\n}\n\n/**\n * Cursor-paginated room history, ordered newest-first regardless of paging direction.\n *\n * `lastSeen` positions the unread divider only. Using it as a pagination bound instead would truncate\n * the page at the marker, which is why the per-type `*.history` endpoints cannot serve this.\n */\nexport async function loadRoomHistory({","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b263243745917337314259cf987c0e989cf0cdc9/apps/meteor/server/lib/messages/loadRoomHistory.ts#L12-L48","documentation":"decodeHistoryCursor throws error-invalid-cursor when the cursor string passed to loadRoomHistory's latest/oldest parameters is not composed purely of digits. The cursor is an epoch-milliseconds timestamp produced by encodeHistoryCursor, so any non-numeric input (UUID, base64 offset cursor, 'null', empty string) is rejected. This guards the room history pagination API against malformed or foreign cursor formats.","triggerScenarios":"Calling the loadRoomHistory meteor method (or the REST endpoint that maps to it) with a latest/oldest value that fails /^\\d+$/ — e.g. latest='abc', latest='', latest='1690000000000abc', or passing an offset-style cursor from another endpoint.","commonSituations":"Mixing cursor formats between API versions, forwarding a cursor from a different service, client sending null/undefined stringified as 'null' or 'undefined', or hand-crafting cursors instead of echoing back the value returned by a previous history response.","solutions":["Echo back exactly the cursor string returned by the previous loadRoomHistory response instead of constructing one","Ensure latest/oldest are numeric epoch-millisecond strings (e.g. String(Date.now()))","Sanitize client-side: strip or reject non-digit cursor values before calling the method","If migrating from an older offset-based pagination, map old offsets to timestamps server-side instead of passing them through"],"exampleFix":"// before\nMeteor.call('loadRoomHistory', { rid, latest: 'page=2' });\n\n// after\nMeteor.call('loadRoomHistory', { rid, latest: String(Date.now()) });","handlingStrategy":"validation","validationCode":"const isValidCursor = (c: unknown): c is string => typeof c === 'string' && /^\\d+$/.test(c) && Number(c) <= 8.64e15;\nif (latest != null && !isValidCursor(latest)) throw new TypeError('latest must be an epoch-ms digit string');","typeGuard":"const isHistoryCursor = (c: unknown): c is string => typeof c === 'string' && /^\\d+$/.test(c) && Number(c) <= 8.64e15;","tryCatchPattern":null,"preventionTips":["Only echo cursors returned by previous loadRoomHistory responses","Standardize on epoch-milliseconds strings for all cursor values","Validate cursor format and numeric range before sending requests"],"tags":["meteor","pagination","cursor","validation"],"backgroundTag":"invalid-cursor-parameter","analyzedSha":"b263243745917337314259cf987c0e989cf0cdc9","analyzedAt":"2026-08-28T13:18:22.752Z","contentChangedAt":"2026-08-28T13:18:22.752Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}