1- import { ChannelCredentials , Metadata , ServiceError } from '@postman/grpc-js' ;
2- import { getDescriptorRootFromDescriptorSet } from './descriptor' ;
3- import * as services from './reflection_grpc_pb' ;
41import {
5- ServerReflectionRequest ,
6- ServerReflectionResponse ,
7- } from './reflection_pb' ;
2+ ChannelCredentials ,
3+ Metadata ,
4+ ServiceError ,
5+ status as GrpcStatus ,
6+ } from '@postman/grpc-js' ;
7+ import { getDescriptorRootFromDescriptorSet } from './descriptor' ;
88import { Root } from '@postman/protobufjs' ;
99import {
1010 FileDescriptorSet ,
@@ -13,26 +13,191 @@ import {
1313} from '@postman/protobufjs/ext/descriptor' ;
1414import set from 'lodash.set' ;
1515
16+ // Static type definitions with common structures across all reflection providers
17+ import type { ServerReflectionClient } from './reflection_providers/v1alpha/reflection_grpc_pb' ;
18+ import type {
19+ ServerReflectionRequest ,
20+ ServerReflectionResponse ,
21+ } from './reflection_providers/v1alpha/reflection_pb' ;
22+
23+ const supportedReflectionAPIVersions = {
24+ v1alpha : {
25+ priority : 0 ,
26+ serviceName : 'grpc.reflection.v1alpha.ServerReflection' ,
27+ client : import ( './reflection_providers/v1alpha/reflection_pb' ) ,
28+ service : import ( './reflection_providers/v1alpha/reflection_grpc_pb' ) ,
29+ } ,
30+ v1 : {
31+ priority : 1 ,
32+ serviceName : 'grpc.reflection.v1.ServerReflection' ,
33+ client : import ( './reflection_providers/v1/reflection_pb' ) ,
34+ service : import ( './reflection_providers/v1/reflection_grpc_pb' ) ,
35+ } ,
36+ } ;
37+
1638export class Client {
1739 metadata : Metadata ;
18- grpcClient : services . IServerReflectionClient ;
1940 private fileDescriptorCache : Map < string , IFileDescriptorProto > = new Map ( ) ;
41+ private url : string ;
42+ private credentials : ChannelCredentials ;
43+ private clientOptions : object | undefined ;
44+
45+ grpcClient : ServerReflectionClient | undefined ;
46+ compatibleReflectionVersion : string | undefined ;
47+ private reflectionResponseCache : ServerReflectionResponse | undefined ;
48+ private CompatibleServerReflectionRequest :
49+ | ( new (
50+ ...args : ConstructorParameters < typeof ServerReflectionRequest >
51+ ) => ServerReflectionRequest )
52+ | undefined ;
53+
2054 constructor (
2155 url : string ,
2256 credentials : ChannelCredentials ,
2357 options ?: object ,
2458 metadata ?: Metadata
2559 ) {
60+ this . url = url ;
61+ this . credentials = credentials ;
62+ this . clientOptions = options ;
2663 this . fileDescriptorCache = new Map ( ) ;
2764 this . metadata = metadata || new Metadata ( ) ;
28- this . grpcClient = new services . ServerReflectionClient (
29- url ,
30- credentials ,
31- options
32- ) ;
3365 }
3466
35- listServices ( ) : Promise < string [ ] > {
67+ private async sendReflectionRequest (
68+ request : ServerReflectionRequest | ServerReflectionRequest [ ] ,
69+ client ?: ServerReflectionClient
70+ ) : Promise < ServerReflectionResponse [ ] > {
71+ return new Promise ( ( resolve , reject ) => {
72+ const result : ServerReflectionResponse [ ] = [ ] ;
73+
74+ const grpcCall = ( client || this . grpcClient ! ) . serverReflectionInfo (
75+ this . metadata
76+ ) ;
77+
78+ grpcCall . on ( 'data' , ( response : ServerReflectionResponse ) => {
79+ result . push ( response ) ;
80+ } ) ;
81+
82+ grpcCall . on ( 'error' , ( error : ServiceError ) => {
83+ reject ( error ) ;
84+ } ) ;
85+
86+ grpcCall . on ( 'end' , ( ) => resolve ( result ) ) ;
87+
88+ if ( Array . isArray ( request ) ) {
89+ request . forEach ( req => grpcCall . write ( req ) ) ;
90+ } else {
91+ grpcCall . write ( request ) ;
92+ }
93+
94+ grpcCall . end ( ) ;
95+ } ) ;
96+ }
97+
98+ private async evaluateSupportedServerReflectionProtocol ( ) {
99+ const evaluationPromises = [ ] ;
100+
101+ // Check version compatibility and initialize gRPC client based on that
102+ for ( const version of Object . keys ( supportedReflectionAPIVersions ) ) {
103+ type ReflectionCheckPromiseReturnType = {
104+ successful : boolean ;
105+ priority : number ;
106+ effect ?: ( ) => void ;
107+ error ?: ServiceError ;
108+ } ;
109+
110+ evaluationPromises . push (
111+ // eslint-disable-next-line no-async-promise-executor
112+ new Promise < ReflectionCheckPromiseReturnType > ( async resolve => {
113+ const protocolConfig =
114+ supportedReflectionAPIVersions [
115+ version as keyof typeof supportedReflectionAPIVersions
116+ ] ;
117+ const {
118+ service : servicePromise ,
119+ client : clientPromise ,
120+ } = protocolConfig ;
121+
122+ const [ protocolService , protocolClient ] = await Promise . all ( [
123+ servicePromise ,
124+ clientPromise ,
125+ ] ) ;
126+
127+ const grpcClientForProtocol = new protocolService . ServerReflectionClient (
128+ this . url ,
129+ this . credentials ,
130+ this . clientOptions
131+ ) ;
132+
133+ const request = new protocolClient . ServerReflectionRequest ( ) ;
134+
135+ request . setListServices ( '*' ) ;
136+
137+ try {
138+ const [ reflectionResponse ] = await this . sendReflectionRequest (
139+ request ,
140+ grpcClientForProtocol
141+ ) ;
142+
143+ return resolve ( {
144+ successful : true ,
145+ priority : protocolConfig . priority ,
146+ effect : ( ) => {
147+ this . grpcClient = grpcClientForProtocol ;
148+ this . compatibleReflectionVersion = version ;
149+ this . CompatibleServerReflectionRequest =
150+ protocolClient . ServerReflectionRequest ;
151+ this . reflectionResponseCache = reflectionResponse ;
152+ } ,
153+ } ) ;
154+ } catch ( error ) {
155+ return resolve ( {
156+ successful : false ,
157+ priority : protocolConfig . priority ,
158+ error : error as ServiceError ,
159+ } ) ;
160+ }
161+ } )
162+ ) ;
163+ }
164+
165+ const evaluationResults = await Promise . all ( evaluationPromises ) ;
166+
167+ const [ successfulReflectionByPriority ] = evaluationResults
168+ . filter ( res => res . successful )
169+ . sort ( ( res1 , res2 ) => res2 . priority - res1 . priority ) ;
170+
171+ if ( ! successfulReflectionByPriority ) {
172+ const reflectionNotImplementedError = evaluationResults . find ( res => {
173+ return res . error && res . error . code === GrpcStatus . UNIMPLEMENTED ;
174+ } ) ;
175+
176+ const resultWithServiceError = evaluationResults . find ( res => {
177+ // Something is actually wrong with the gRPC service
178+ return res . error && res . error . code !== GrpcStatus . UNIMPLEMENTED ;
179+ } ) ;
180+
181+ throw (
182+ resultWithServiceError ?. error ||
183+ reflectionNotImplementedError ?. error ||
184+ new Error ( 'No compatible reflection API found.' )
185+ ) ;
186+ }
187+
188+ // Set grpc client and other properties based on highest priority successful version
189+ successfulReflectionByPriority . effect ! ( ) ;
190+ }
191+
192+ async initialize ( ) {
193+ if ( this . grpcClient || this . compatibleReflectionVersion ) return ;
194+
195+ await this . evaluateSupportedServerReflectionProtocol ( ) ;
196+ }
197+
198+ async listServices ( ) : Promise < string [ ] > {
199+ await this . initialize ( ) ;
200+
36201 return new Promise ( ( resolve , reject ) => {
37202 function dataCallback ( response : ServerReflectionResponse ) {
38203 if ( response . hasListServicesResponse ( ) ) {
@@ -52,14 +217,16 @@ export class Client {
52217 reject ( e ) ;
53218 }
54219
55- const request = new ServerReflectionRequest ( ) ;
220+ if ( this . reflectionResponseCache ) {
221+ return dataCallback ( this . reflectionResponseCache ) ;
222+ }
223+
224+ const request = new this . CompatibleServerReflectionRequest ! ( ) ;
56225 request . setListServices ( '*' ) ;
57226
58- const grpcCall = this . grpcClient . serverReflectionInfo ( this . metadata ) ;
59- grpcCall . on ( 'data' , dataCallback ) ;
60- grpcCall . on ( 'error' , errorCallback ) ;
61- grpcCall . write ( request ) ;
62- grpcCall . end ( ) ;
227+ this . sendReflectionRequest ( request )
228+ . then ( ( [ response ] ) => dataCallback ( response ) )
229+ . catch ( errorCallback ) ;
63230 } ) ;
64231 }
65232
@@ -120,9 +287,11 @@ export class Client {
120287 return fileDescriptorMap ;
121288 }
122289
123- private getFileContainingSymbol (
290+ private async getFileContainingSymbol (
124291 symbol : string
125292 ) : Promise < Array < IFileDescriptorProto > | undefined > {
293+ await this . initialize ( ) ;
294+
126295 const fileDescriptorCache = this . fileDescriptorCache ;
127296 return new Promise ( ( resolve , reject ) => {
128297 function dataCallback ( response : ServerReflectionResponse ) {
@@ -154,20 +323,20 @@ export class Client {
154323 reject ( e ) ;
155324 }
156325
157- const request = new ServerReflectionRequest ( ) ;
326+ const request = new this . CompatibleServerReflectionRequest ! ( ) ;
158327 request . setFileContainingSymbol ( symbol ) ;
159328
160- const grpcCall = this . grpcClient . serverReflectionInfo ( this . metadata ) ;
161- grpcCall . on ( 'data' , dataCallback ) ;
162- grpcCall . on ( 'error' , errorCallback ) ;
163- grpcCall . write ( request ) ;
164- grpcCall . end ( ) ;
329+ this . sendReflectionRequest ( request )
330+ . then ( ( [ response ] ) => dataCallback ( response ) )
331+ . catch ( errorCallback ) ;
165332 } ) ;
166333 }
167334
168- private getFilesByFilenames (
335+ private async getFilesByFilenames (
169336 symbols : string [ ]
170337 ) : Promise < Array < IFileDescriptorProto > | undefined > {
338+ await this . initialize ( ) ;
339+
171340 const result : Array < IFileDescriptorProto > = [ ] ;
172341 const fileDescriptorCache = this . fileDescriptorCache ;
173342 const symbolsToFetch = symbols . filter ( symbol => {
@@ -203,6 +372,13 @@ export class Client {
203372 result . push ( fileDescriptorProto ) ;
204373 }
205374 } ) ;
375+ } else if ( response . hasErrorResponse ( ) ) {
376+ const err = response . getErrorResponse ( ) ;
377+ reject (
378+ new Error (
379+ `Error: ${ err ?. getErrorCode ( ) } : ${ err ?. getErrorMessage ( ) } `
380+ )
381+ ) ;
206382 } else {
207383 reject ( Error ( ) ) ;
208384 }
@@ -212,19 +388,17 @@ export class Client {
212388 reject ( e ) ;
213389 }
214390
215- const grpcCall = this . grpcClient . serverReflectionInfo ( this . metadata ) ;
216- grpcCall . on ( 'data' , dataCallback ) ;
217- grpcCall . on ( 'error' , errorCallback ) ;
218- grpcCall . on ( 'end' , ( ) => {
219- resolve ( result ) ;
391+ const requests = symbolsToFetch . map ( symbol => {
392+ const request = new this . CompatibleServerReflectionRequest ! ( ) ;
393+ return request . setFileByFilename ( symbol ) ;
220394 } ) ;
221395
222- symbolsToFetch . forEach ( symbol => {
223- const request = new ServerReflectionRequest ( ) ;
224- grpcCall . write ( request . setFileByFilename ( symbol ) ) ;
225- } ) ;
226-
227- grpcCall . end ( ) ;
396+ this . sendReflectionRequest ( requests )
397+ . then ( responses => {
398+ for ( const dataBit of responses ) dataCallback ( dataBit ) ;
399+ resolve ( result ) ;
400+ } )
401+ . catch ( errorCallback ) ;
228402 } ) ;
229403 }
230404}
0 commit comments