@goplus123/core-api 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -260,11 +260,19 @@ const sdk = initSDK({
260
260
 
261
261
  对应实现:[`initSDK`](./src/index.ts#L372-L578)
262
262
 
263
- ### React Native:注入 gRPC invoker(解决 RN 真机解析 gRPC-Web 响应问题)
263
+ ### React Native:配置 gRPC invoker(Invoke)(解决 RN 真机 gRPC-Web 兼容问题)
264
264
 
265
265
  当后端仅提供 gRPC-Web,而 React Native 环境下 `@connectrpc/connect-web` 的 `fetch`/二进制响应解析存在兼容性问题时,可以通过 `grpc.invoker` 注入一层“可替换的底层调用器”,在不改动上层调用方式(`sdk.xxx` / `requestApi` / spec)前提下,切换为 RN 可用的 gRPC-Web 实现。
266
266
 
267
267
  这个 `invoker` 只是一个“可插拔的 unary 调用实现”,不限定必须使用某个第三方库。
268
+ 内置的 `builtin` invoker 使用的是 gRPC-Web(二进制)协议:`application/grpc-web+proto`。
269
+
270
+ `grpc.invoker` 支持两种形态(优先级从高到低):
271
+
272
+ - `invoker: GrpcInvoker`:使用外部传入的自定义 invoker
273
+ - `invoker: 'builtin'`:使用 SDK 内置的 gRPC-Web unary invoker(基于 `fetch` + `toBinary/fromBinary`)
274
+
275
+ 注意:使用 `invoker: 'builtin'` 时,请确保 `grpc.protocol` 为 `'grpc-web'`。
268
276
 
269
277
  `invoker.unary()` 的入参约定:
270
278
 
@@ -275,7 +283,37 @@ const sdk = initSDK({
275
283
  - `headers`:最终要发出去的 headers(已合并 auth/getHeaders/静态 headers 与 callOptions.headers)
276
284
  - `callOptions`:透传的调用选项(例如 timeout),按你的底层实现自行使用
277
285
 
278
- 示例 A(推荐):在 RN 工程内用 `fetch` 走 gRPC-Web(二进制),复用本 SDK 的 bufbuild 生成物(不需要重新编译 proto)。这个方案不需要引入 `@improbable-eng/grpc-web-react-native-transport`:
286
+ 示例 0(推荐):直接启用内置 invoker(不需要在 RN 工程维护一套 invoke 代码):
287
+
288
+ ```ts
289
+ import { initSDK, fetchGuid } from '@goplus123/core-api'
290
+
291
+ const sdk = initSDK({
292
+ ws: {
293
+ url: 'wss://example.com/websocket',
294
+ protocols: fetchGuid(),
295
+ },
296
+ grpc: {
297
+ baseUrl: 'https://example.com/cms',
298
+ protocol: 'grpc-web',
299
+ invoker: 'builtin',
300
+ },
301
+ http: {
302
+ baseUrl: 'https://example.com/api',
303
+ },
304
+ headerConfig: {
305
+ version: '1.0.0',
306
+ platformId: '50',
307
+ isReload: false,
308
+ deviceType: 1,
309
+ deviceId: '',
310
+ childPlatformId: '50',
311
+ },
312
+ defaultTransport: 'auto',
313
+ })
314
+ ```
315
+
316
+ 示例 A(自定义):在 RN 工程内用 `fetch` 走 gRPC-Web(二进制),复用本 SDK 的 bufbuild 生成物(不需要重新编译 proto)。这个方案不需要引入 `@improbable-eng/grpc-web-react-native-transport`:
279
317
 
280
318
  ```ts
281
319
  import { initSDK, fetchGuid } from '@goplus123/core-api'
@@ -352,6 +390,49 @@ function capitalizeRpcName(methodName: string): string {
352
390
  return methodName[0]!.toUpperCase() + methodName.slice(1)
353
391
  }
354
392
 
393
+ function resolveServiceMethod(service: any, methodName: string, method?: any): any | undefined {
394
+ if (method) return method
395
+
396
+ const expectedLocalName = methodName
397
+ const expectedRpcName = capitalizeRpcName(methodName)
398
+
399
+ const direct = service?.method
400
+ if (Array.isArray(direct)) {
401
+ return direct.find((m: any) => {
402
+ const localName = String(m?.localName ?? '')
403
+ const rpcName = String(m?.name ?? '')
404
+ return (
405
+ localName === expectedLocalName ||
406
+ localName === expectedRpcName ||
407
+ rpcName === expectedLocalName ||
408
+ rpcName === expectedRpcName
409
+ )
410
+ })
411
+ }
412
+ if (direct && typeof direct === 'object') {
413
+ return (direct as any)[expectedLocalName] ?? (direct as any)[expectedRpcName]
414
+ }
415
+
416
+ const methods = service?.methods
417
+ if (Array.isArray(methods)) {
418
+ return methods.find((m: any) => {
419
+ const localName = String(m?.localName ?? '')
420
+ const rpcName = String(m?.name ?? '')
421
+ return (
422
+ localName === expectedLocalName ||
423
+ localName === expectedRpcName ||
424
+ rpcName === expectedLocalName ||
425
+ rpcName === expectedRpcName
426
+ )
427
+ })
428
+ }
429
+ if (methods && typeof methods === 'object') {
430
+ return (methods as any)[expectedLocalName] ?? (methods as any)[expectedRpcName]
431
+ }
432
+
433
+ return undefined
434
+ }
435
+
355
436
  function getGrpcWebPath(service: any, methodName: string, method: any): string {
356
437
  const typeName = String(service?.typeName ?? '')
357
438
  const rpcName = String(method?.name ?? method?.rpc?.name ?? '') || capitalizeRpcName(methodName)
@@ -367,14 +448,7 @@ async function rnGrpcWebUnary(args: {
367
448
  headers: Record<string, string>
368
449
  callOptions?: { timeoutMs?: number }
369
450
  }): Promise<any> {
370
- const method =
371
- args.method ??
372
- (() => {
373
- const ms = args.service?.methods
374
- if (Array.isArray(ms)) return ms.find((m: any) => m?.localName === args.methodName || m?.name === args.methodName)
375
- if (ms && typeof ms === 'object') return (ms as any)[args.methodName]
376
- return undefined
377
- })()
451
+ const method = resolveServiceMethod(args.service, args.methodName, args.method)
378
452
  if (!method) {
379
453
  throw new Error(`gRPC method not found: ${String(args.service?.typeName ?? '')}.${args.methodName}`)
380
454
  }
@@ -447,7 +521,10 @@ const sdk = initSDK({
447
521
  })
448
522
  ```
449
523
 
450
- 补充说明:如果你已经在 RN 工程里有基于 `@improbable-eng/grpc-web` / `@improbable-eng/grpc-web-react-native-transport` 的 `rnGrpcWebUnary` 封装,也可以直接把它塞进 `invoker.unary`。但需要确保它能消费本 SDK 生成的 bufbuild message 与 schema(`toBinary/fromBinary`),否则通常会因为“message/descriptor 体系不一致”(improbable 常见搭配是另一套 proto 生成物)而对不上类型与序列化方式。
524
+ 补充说明:
525
+
526
+ - 自定义 invoker 时,建议优先使用 `args.method`(SDK 解析好的方法描述),避免在 RN 侧重复解析 `service.method/service.methods` 的结构差异。
527
+ - 如果你已经在 RN 工程里有基于 `@improbable-eng/grpc-web` / `@improbable-eng/grpc-web-react-native-transport` 的 `rnGrpcWebUnary` 封装,也可以直接把它塞进 `invoker.unary`。但需要确保它能消费本 SDK 生成的 bufbuild message 与 schema(`toBinary/fromBinary`),否则通常会因为“message/descriptor 体系不一致”(improbable 常见搭配是另一套 proto 生成物)而对不上类型与序列化方式。
451
528
 
452
529
  ### 2) 直接调用(推荐)
453
530
 
package/dist/index.cjs CHANGED
@@ -39,7 +39,174 @@ module.exports = __toCommonJS(index_exports);
39
39
  // src/client/grpcClient.ts
40
40
  var import_connect = require("@connectrpc/connect");
41
41
  var import_connect_web = require("@connectrpc/connect-web");
42
+ var import_protobuf2 = require("@bufbuild/protobuf");
43
+
44
+ // src/client/invokers/builtinGrpcWebInvoker.ts
42
45
  var import_protobuf = require("@bufbuild/protobuf");
46
+ function createBuiltinGrpcWebInvoker(options) {
47
+ const fetchImpl = options.fetch ?? globalThis.fetch;
48
+ const logger = options.logger;
49
+ if (!fetchImpl) {
50
+ return {
51
+ unary: async () => {
52
+ throw new Error("fetch is not available in current environment");
53
+ }
54
+ };
55
+ }
56
+ const grpcWebEncodeUnary = (messageBytes) => {
57
+ const out = new Uint8Array(5 + messageBytes.byteLength);
58
+ out[0] = 0;
59
+ const view = new DataView(out.buffer, out.byteOffset, out.byteLength);
60
+ view.setUint32(1, messageBytes.byteLength, false);
61
+ out.set(messageBytes, 5);
62
+ return out;
63
+ };
64
+ const bytesToAscii = (bytes) => {
65
+ let s = "";
66
+ for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
67
+ return s;
68
+ };
69
+ const parseGrpcWebTrailers = (bytes) => {
70
+ const text = bytesToAscii(bytes);
71
+ const out = {};
72
+ for (const line of text.split("\r\n")) {
73
+ if (!line) continue;
74
+ const idx = line.indexOf(":");
75
+ if (idx < 0) continue;
76
+ const k = line.slice(0, idx).trim().toLowerCase();
77
+ const v = line.slice(idx + 1).trim();
78
+ out[k] = v;
79
+ }
80
+ return out;
81
+ };
82
+ const grpcWebDecodeUnary = (body) => {
83
+ let offset = 0;
84
+ let message;
85
+ let trailers = {};
86
+ while (offset + 5 <= body.byteLength) {
87
+ const flags = body[offset];
88
+ const length = new DataView(body.buffer, body.byteOffset + offset + 1, 4).getUint32(0, false);
89
+ offset += 5;
90
+ if (offset + length > body.byteLength) break;
91
+ const frame = body.subarray(offset, offset + length);
92
+ offset += length;
93
+ const isTrailer = (flags & 128) === 128;
94
+ if (isTrailer) {
95
+ trailers = { ...trailers, ...parseGrpcWebTrailers(frame) };
96
+ } else if (message == null) {
97
+ message = frame;
98
+ }
99
+ }
100
+ return { message, trailers };
101
+ };
102
+ const joinUrl = (baseUrl, path) => {
103
+ return baseUrl.replace(/\/+$/, "") + "/" + path.replace(/^\/+/, "");
104
+ };
105
+ const capitalizeRpcName = (methodName) => {
106
+ if (!methodName) return methodName;
107
+ return methodName[0].toUpperCase() + methodName.slice(1);
108
+ };
109
+ const getGrpcWebPath = (service, methodName, method) => {
110
+ const typeName = String(service?.typeName ?? "");
111
+ const rpcName = String(method?.name ?? "") || capitalizeRpcName(methodName);
112
+ return `${typeName}/${rpcName}`;
113
+ };
114
+ const safeDecodeGrpcMessage = (value) => {
115
+ if (!value) return value;
116
+ try {
117
+ return decodeURIComponent(value);
118
+ } catch {
119
+ return value;
120
+ }
121
+ };
122
+ return {
123
+ unary: async (args) => {
124
+ const service = String(args.service?.typeName ?? "");
125
+ const startedAt = Date.now();
126
+ let url;
127
+ const contentType = "application/grpc-web+proto";
128
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
129
+ const timeoutMs = args.callOptions?.timeoutMs;
130
+ const timeoutId = controller && typeof timeoutMs === "number" ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
131
+ try {
132
+ const method = args.method;
133
+ if (!method) {
134
+ throw new Error(`gRPC method not found: ${service}.${args.methodName}`);
135
+ }
136
+ url = joinUrl(args.baseUrl, getGrpcWebPath(args.service, args.methodName, method));
137
+ const requestBytes = (0, import_protobuf.toBinary)(method.input, args.request);
138
+ const body = grpcWebEncodeUnary(requestBytes);
139
+ logger?.debug?.("[Grpc:Invoker:Builtin:Start]", {
140
+ service,
141
+ methodName: args.methodName,
142
+ baseUrl: args.baseUrl,
143
+ url,
144
+ timeoutMs,
145
+ requestBytes: requestBytes.byteLength
146
+ });
147
+ const res = await fetchImpl(url, {
148
+ method: "POST",
149
+ headers: {
150
+ ...args.headers,
151
+ "content-type": contentType,
152
+ accept: contentType,
153
+ "x-grpc-web": "1"
154
+ },
155
+ body: body.buffer,
156
+ signal: controller?.signal
157
+ });
158
+ logger?.debug?.("[Grpc:Invoker:Builtin:Http]", {
159
+ service,
160
+ methodName: args.methodName,
161
+ url,
162
+ ok: res.ok,
163
+ status: res.status,
164
+ statusText: res.statusText,
165
+ durationMs: Date.now() - startedAt
166
+ });
167
+ if (!res.ok) {
168
+ throw new Error(`gRPC fetch failed: ${res.status} ${res.statusText}`);
169
+ }
170
+ const raw = new Uint8Array(await res.arrayBuffer());
171
+ const decoded = grpcWebDecodeUnary(raw);
172
+ const statusFromHeaders = typeof res.headers?.get === "function" ? res.headers.get("grpc-status") : void 0;
173
+ const msgFromHeaders = typeof res.headers?.get === "function" ? res.headers.get("grpc-message") : void 0;
174
+ const grpcStatus = decoded.trailers["grpc-status"] ?? statusFromHeaders ?? "0";
175
+ const grpcMessage = safeDecodeGrpcMessage(
176
+ decoded.trailers["grpc-message"] ?? msgFromHeaders
177
+ );
178
+ logger?.debug?.("[Grpc:Invoker:Builtin:End]", {
179
+ service,
180
+ methodName: args.methodName,
181
+ url,
182
+ grpcStatus,
183
+ grpcMessage,
184
+ durationMs: Date.now() - startedAt
185
+ });
186
+ if (grpcStatus !== "0") {
187
+ const msg = grpcMessage ?? "gRPC request failed";
188
+ throw new Error(`gRPC status=${grpcStatus} message=${msg}`);
189
+ }
190
+ if (!decoded.message) throw new Error("gRPC response message missing");
191
+ return (0, import_protobuf.fromBinary)(method.output, decoded.message);
192
+ } catch (error) {
193
+ logger?.error?.("[Grpc:Invoker:Builtin:Error]", {
194
+ service,
195
+ methodName: args.methodName,
196
+ baseUrl: args.baseUrl,
197
+ url,
198
+ durationMs: Date.now() - startedAt,
199
+ error
200
+ });
201
+ throw error;
202
+ } finally {
203
+ if (timeoutId) clearTimeout(timeoutId);
204
+ }
205
+ }
206
+ };
207
+ }
208
+
209
+ // src/client/grpcClient.ts
43
210
  var GrpcApiError = class extends Error {
44
211
  name = "GrpcApiError";
45
212
  service;
@@ -70,6 +237,7 @@ var GrpcClient = class {
70
237
  getHeaders;
71
238
  logger;
72
239
  invoker;
240
+ invokerMode;
73
241
  invokerClientByKey;
74
242
  constructor(config) {
75
243
  this.baseUrl = config.baseUrl;
@@ -80,7 +248,6 @@ var GrpcClient = class {
80
248
  this.getToken = config.getToken;
81
249
  this.headers = config.headers;
82
250
  this.getHeaders = config.getHeaders;
83
- this.invoker = config.invoker;
84
251
  this.logger = config.logger ?? (config.debug ? { debug: console.log, info: console.log, warn: console.warn, error: console.error } : void 0);
85
252
  this.interceptors = [
86
253
  this.createRpcFromInterceptor(),
@@ -91,6 +258,8 @@ var GrpcClient = class {
91
258
  this.transportByBaseUrl = /* @__PURE__ */ new Map();
92
259
  this.transport = this.createTransport(this.baseUrl);
93
260
  this.invokerClientByKey = /* @__PURE__ */ new Map();
261
+ this.invokerMode = config.invoker === "builtin" ? "builtin" : config.invoker ? "custom" : "none";
262
+ this.invoker = config.invoker === "builtin" ? createBuiltinGrpcWebInvoker({ fetch: this.fetchImpl, logger: this.logger }) : config.invoker;
94
263
  }
95
264
  createTransport(baseUrl) {
96
265
  const cached = this.transportByBaseUrl.get(baseUrl);
@@ -186,20 +355,43 @@ var GrpcClient = class {
186
355
  getInvokerClientKey(service, baseUrl) {
187
356
  return `${String(service?.typeName ?? "")}@@${baseUrl}`;
188
357
  }
189
- getServiceMethodDesc(service, methodName) {
358
+ getServiceMethodMap(service) {
359
+ const out = {};
360
+ const addMethod = (m) => {
361
+ if (!m) return;
362
+ const localName = m.localName != null ? String(m.localName) : "";
363
+ const name = m.name != null ? String(m.name) : "";
364
+ if (localName) out[localName] = m;
365
+ if (name && !(name in out)) out[name] = m;
366
+ };
367
+ const direct = service?.method;
368
+ if (direct && typeof direct === "object") {
369
+ for (const [k, v] of Object.entries(direct)) {
370
+ if (v) out[k] = v;
371
+ addMethod(v);
372
+ }
373
+ }
190
374
  const methods = service?.methods;
191
- if (!methods) return void 0;
192
375
  if (Array.isArray(methods)) {
193
- for (const m of methods) {
194
- if (!m) continue;
195
- const localName = String(m.localName ?? "");
196
- const name = String(m.name ?? "");
197
- if (localName === methodName || name === methodName) return m;
376
+ for (const m of methods) addMethod(m);
377
+ } else if (methods && typeof methods === "object") {
378
+ for (const [k, v] of Object.entries(methods)) {
379
+ if (v) out[k] = v;
380
+ addMethod(v);
198
381
  }
199
- return void 0;
200
382
  }
201
- if (typeof methods === "object") {
202
- return methods[methodName];
383
+ return Object.keys(out).length > 0 ? out : void 0;
384
+ }
385
+ getServiceMethodDesc(service, methodName) {
386
+ const methodMap = this.getServiceMethodMap(service);
387
+ if (!methodMap) return void 0;
388
+ const direct = methodMap[methodName];
389
+ if (direct) return direct;
390
+ for (const m of Object.values(methodMap)) {
391
+ if (!m) continue;
392
+ const localName = m.localName != null ? String(m.localName) : "";
393
+ const name = m.name != null ? String(m.name) : "";
394
+ if (localName === methodName || name === methodName) return m;
203
395
  }
204
396
  return void 0;
205
397
  }
@@ -208,24 +400,7 @@ var GrpcClient = class {
208
400
  const key = this.getInvokerClientKey(service, baseUrl);
209
401
  const cached = this.invokerClientByKey.get(key);
210
402
  if (cached) return cached;
211
- const methods = service?.methods;
212
- const hasMethods = methods != null;
213
- const methodMap = (() => {
214
- if (!methods) return void 0;
215
- if (Array.isArray(methods)) {
216
- const out = {};
217
- for (const m of methods) {
218
- if (!m) continue;
219
- const localName = String(m.localName ?? "");
220
- const name = String(m.name ?? "");
221
- if (localName) out[localName] = m;
222
- if (name && !(name in out)) out[name] = m;
223
- }
224
- return out;
225
- }
226
- if (typeof methods === "object") return methods;
227
- return void 0;
228
- })();
403
+ const methodMap = this.getServiceMethodMap(service);
229
404
  const typeName = String(service?.typeName ?? "");
230
405
  const client = new Proxy(
231
406
  {},
@@ -233,7 +408,7 @@ var GrpcClient = class {
233
408
  get: (_target, prop) => {
234
409
  if (prop === "then") return void 0;
235
410
  if (typeof prop !== "string") return void 0;
236
- if (hasMethods && methodMap && !(prop in methodMap)) {
411
+ if (methodMap && !(prop in methodMap)) {
237
412
  return async () => {
238
413
  throw new Error(`gRPC method not found: ${typeName}.${prop}`);
239
414
  };
@@ -252,6 +427,12 @@ var GrpcClient = class {
252
427
  const baseUrl = this.pickBaseUrl(typeName);
253
428
  try {
254
429
  if (this.invoker) {
430
+ this.logger?.debug?.("[Grpc:Invoker]", {
431
+ mode: this.invokerMode,
432
+ service: typeName,
433
+ methodName,
434
+ baseUrl
435
+ });
255
436
  const method2 = this.getServiceMethodDesc(service, methodName);
256
437
  const headers = await this.attachInvokerHeaders(
257
438
  this.normalizeHeaders(options?.headers),
@@ -313,7 +494,7 @@ var GrpcClient = class {
313
494
  return (0, import_connect.createClient)(service, transport);
314
495
  }
315
496
  unary(schema, payload, call, options) {
316
- const req = (0, import_protobuf.create)(schema, payload);
497
+ const req = (0, import_protobuf2.create)(schema, payload);
317
498
  return call(req, options);
318
499
  }
319
500
  /**
@@ -1507,7 +1688,7 @@ var WsRpcClient = class {
1507
1688
  };
1508
1689
 
1509
1690
  // src/client/unifiedApiClient.ts
1510
- var import_protobuf2 = require("@bufbuild/protobuf");
1691
+ var import_protobuf3 = require("@bufbuild/protobuf");
1511
1692
 
1512
1693
  // src/client/interceptors.ts
1513
1694
  function isBizError(err) {
@@ -1744,7 +1925,7 @@ var UnifiedApiClient = class {
1744
1925
  throw new Error(`gRPC method not found: ${service.typeName}.${methodName}`);
1745
1926
  }
1746
1927
  const callOptions = ctx.meta?.callOptions;
1747
- const req = endpoint.grpc.requestSchema ? (0, import_protobuf2.create)(endpoint.grpc.requestSchema, ctx.request ?? {}) : ctx.request;
1928
+ const req = endpoint.grpc.requestSchema ? (0, import_protobuf3.create)(endpoint.grpc.requestSchema, ctx.request ?? {}) : ctx.request;
1748
1929
  const headers = { ...callOptions?.headers ?? {}, ...ctx.headers ?? {} };
1749
1930
  return await method(req, { ...callOptions ?? {}, headers });
1750
1931
  }
@@ -1969,7 +2150,7 @@ var file_auth_MayaService = /* @__PURE__ */ (0, import_codegenv241.fileDesc)("Ch
1969
2150
  var MayaService = /* @__PURE__ */ (0, import_codegenv241.serviceDesc)(file_auth_MayaService, 0);
1970
2151
 
1971
2152
  // src/modules/auth.ts
1972
- var import_protobuf3 = require("@bufbuild/protobuf");
2153
+ var import_protobuf4 = require("@bufbuild/protobuf");
1973
2154
  var authService = (grpc) => {
1974
2155
  const client = grpc.getService(FrontendAuthService);
1975
2156
  const faceId = grpc.getService(FrontendFaceIdService);