diff --git a/custom-typings/rawproto.d.ts b/custom-typings/rawproto.d.ts new file mode 100644 index 00000000..f5be30c6 --- /dev/null +++ b/custom-typings/rawproto.d.ts @@ -0,0 +1,81 @@ +declare module 'rawproto' { + /** + * RawProto class for parsing raw protobuf binary data without a schema. + * Migrated from deprecated rawprotoparse package. + */ + export class RawProto { + constructor(data: Uint8Array | ArrayBuffer | Buffer | number[]); + + /** + * Convert the parsed protobuf data to a JavaScript object. + * @param queryMap - Optional query map for field name/type mapping + * @param prefix - Field name prefix (default: 'f') + * @param nameMap - Optional name map + * @param typeMap - Optional type map + */ + toJS( + queryMap?: Record, + prefix?: string, + nameMap?: Record, + typeMap?: Record + ): Record; + + /** + * Generate a .proto schema file from the parsed data. + */ + toProto( + queryMap?: Record, + prefix?: string, + nameMap?: Record, + typeMap?: Record, + messageName?: string + ): string; + + /** + * Query specific fields using path:type format. + */ + query(...queries: string[]): any[]; + + /** + * Sub-messages indexed by field number. + */ + sub: Record; + + /** + * Field counts. + */ + fields: Record; + + /** + * The value as a string, if it looks like a string. + */ + string?: string; + + /** + * The value as an integer. + */ + int?: number; + + /** + * The value as a float. + */ + float?: number; + + /** + * The value as a boolean. + */ + bool?: boolean; + + /** + * The raw bytes. + */ + bytes?: Buffer; + + /** + * Whether the value is likely a string. + */ + likelyString?: boolean; + } + + export default RawProto; +} diff --git a/package-lock.json b/package-lock.json index 41155493..9512d87e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,7 +91,7 @@ "posthog-js": "^1.57.2", "qrcode.react": "^4.2.0", "randexp": "^0.5.3", - "rawprotoparse": "^0.0.9", + "rawproto": "^1.0.3", "react": "^18.2.0", "react-autosuggest": "^10.0.4", "react-beautiful-dnd": "^13.1.1", @@ -10296,6 +10296,18 @@ "node": ">= 0.4" } }, + "node_modules/get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", @@ -16626,10 +16638,31 @@ "node": ">= 0.8" } }, - "node_modules/rawprotoparse": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/rawprotoparse/-/rawprotoparse-0.0.9.tgz", - "integrity": "sha512-0nAJnY2Ii888B882F2TgJ2v2GhT8pzHVVfRRjt05/IR0t65IqjTcoC7szsoDRibghgHacI8vw28ojmPWPmv1Tg==" + "node_modules/rawproto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rawproto/-/rawproto-1.0.3.tgz", + "integrity": "sha512-aSzVgOa49nlzeKcHhCy9NdRB+TjIjF+uwvEjmojONH9C5jw1s4A3KepCYXC0ydrCdRm0KkpGcQiwMs0KHZRcdQ==", + "license": "MIT", + "dependencies": { + "flat": "^6.0.1", + "get-stdin": "^9.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "rawproto": "rawproto_cli.js" + } + }, + "node_modules/rawproto/node_modules/flat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-6.0.1.tgz", + "integrity": "sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + }, + "engines": { + "node": ">=18" + } }, "node_modules/rc": { "version": "1.2.8", @@ -30179,6 +30212,11 @@ "es-object-atoms": "^1.0.0" } }, + "get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==" + }, "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", @@ -34815,10 +34853,22 @@ } } }, - "rawprotoparse": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/rawprotoparse/-/rawprotoparse-0.0.9.tgz", - "integrity": "sha512-0nAJnY2Ii888B882F2TgJ2v2GhT8pzHVVfRRjt05/IR0t65IqjTcoC7szsoDRibghgHacI8vw28ojmPWPmv1Tg==" + "rawproto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rawproto/-/rawproto-1.0.3.tgz", + "integrity": "sha512-aSzVgOa49nlzeKcHhCy9NdRB+TjIjF+uwvEjmojONH9C5jw1s4A3KepCYXC0ydrCdRm0KkpGcQiwMs0KHZRcdQ==", + "requires": { + "flat": "^6.0.1", + "get-stdin": "^9.0.0", + "yargs": "^17.7.2" + }, + "dependencies": { + "flat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-6.0.1.tgz", + "integrity": "sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==" + } + } }, "rc": { "version": "1.2.8", diff --git a/package.json b/package.json index 5fe7f511..52d4b47e 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "posthog-js": "^1.57.2", "qrcode.react": "^4.2.0", "randexp": "^0.5.3", - "rawprotoparse": "^0.0.9", + "rawproto": "^1.0.3", "react": "^18.2.0", "react-autosuggest": "^10.0.4", "react-beautiful-dnd": "^13.1.1", diff --git a/src/util/protobuf.ts b/src/util/protobuf.ts index dfa0e470..c06e67e6 100644 --- a/src/util/protobuf.ts +++ b/src/util/protobuf.ts @@ -1,9 +1,107 @@ -import parseRawProto from 'rawprotoparse'; +/// +import RawProto from 'rawproto'; import { gunzipSync, inflateSync } from 'zlib'; import { Headers } from '../types'; import { getHeaderValue } from '../model/http/headers'; +/** + * Recursively converts a RawProto tree to a simple JS object. + * This mirrors the output format of the deprecated rawprotoparse package. + * + * The old rawprotoparse returned: + * - Single values directly: { "1": "Hello World" } + * - Nested messages as objects: { "1": { "2": "value" } } + * - Buffers for binary data + * + * @param protoNode - A RawProto node to convert + * @returns A plain JavaScript object with field numbers as keys + */ +function convertRawProtoToObject(protoNode: RawProto): Record { + const result: Record = {}; + + if (!protoNode.sub) { + return result; + } + + for (const fieldNum of Object.keys(protoNode.sub)) { + const fields = protoNode.sub[fieldNum]; + + if (fields.length === 1) { + const field = fields[0]; + + let handled = false; + + // Try string first + if (field.likelyString !== undefined ? field.likelyString : false) { + try { + const str = field.string; + if (str !== undefined) { + result[fieldNum] = str; + handled = true; + } + } catch (e) {} + } + + if (handled) continue; + + // Check if it's a sub-message (has its own sub fields) + if (field.sub && Object.keys(field.sub).length > 0) { + result[fieldNum] = convertRawProtoToObject(field); + continue; + } + + // Try int for varint types + try { + const num = field.int; + if (num !== undefined && typeof num === 'number' && Number.isSafeInteger(num)) { + result[fieldNum] = num; + continue; + } + } catch (e) {} + + // Fall back to bytes + try { + const bytes = field.bytes; + if (bytes !== undefined) { + result[fieldNum] = Buffer.from(bytes); + continue; + } + } catch (e) {} + + result[fieldNum] = null; + } else { + // Multiple values - create an array + result[fieldNum] = fields.map((field: any) => { + if (field.likelyString !== undefined ? field.likelyString : false) { + try { + const str = field.string; + if (str !== undefined) return str; + } catch (e) {} + } + + if (field.sub && Object.keys(field.sub).length > 0) { + return convertRawProtoToObject(field); + } + + try { + const num = field.int; + if (num !== undefined && typeof num === 'number' && Number.isSafeInteger(num)) return num; + } catch (e) {} + + try { + const bytes = field.bytes; + if (bytes !== undefined) return Buffer.from(bytes); + } catch (e) {} + + return null; + }); + } + } + + return result; +} + export function isProbablyProtobuf(input: Uint8Array) { // Protobuf data starts with a varint, consisting of a // field number in [1, 2^29[ and a field type in [0, 5]*. @@ -36,7 +134,22 @@ export function isProbablyProtobuf(input: Uint8Array) { [0, 1, 2, 5].includes(fieldType); } -export const parseRawProtobuf = parseRawProto; +/** + * Parse raw protobuf binary data into a JavaScript object. + * This is a wrapper around rawproto that maintains backward compatibility + * with the old rawprotoparse API. + * + * @param input - The protobuf binary data to parse + * @param _options - Options object (for backward compatibility, currently unused) + * @returns Parsed protobuf data as a JavaScript object + */ +export function parseRawProtobuf( + input: Uint8Array | Buffer, + _options?: { prefix?: string } +): any { + const rawProto = new RawProto(input); + return convertRawProtoToObject(rawProto); +} // GRPC message structure: // Ref: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md