Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions custom-typings/rawproto.d.ts
Original file line number Diff line number Diff line change
@@ -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<string, string>,
prefix?: string,
nameMap?: Record<string, string>,
typeMap?: Record<string, string>
): Record<string, any>;

/**
* Generate a .proto schema file from the parsed data.
*/
toProto(
queryMap?: Record<string, string>,
prefix?: string,
nameMap?: Record<string, string>,
typeMap?: Record<string, string>,
messageName?: string
): string;

/**
* Query specific fields using path:type format.
*/
query(...queries: string[]): any[];

/**
* Sub-messages indexed by field number.
*/
sub: Record<string, RawProto[]>;

/**
* Field counts.
*/
fields: Record<number, number>;

/**
* 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;
}
68 changes: 59 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
117 changes: 115 additions & 2 deletions src/util/protobuf.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,107 @@
import parseRawProto from 'rawprotoparse';
/// <reference path="../../custom-typings/rawproto.d.ts" />
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<string, any> {
const result: Record<string, any> = {};

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]*.
Expand Down Expand Up @@ -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
Expand Down