@dronedeploy/rocos-js-sdk 3.1.0 → 3.1.2

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.
@@ -0,0 +1,28 @@
1
+ import { GrpcWebFetchTransport, GrpcWebOptions } from '@protobuf-ts/grpcweb-transport';
2
+ import type { MethodInfo, RpcOptions, ServerStreamingCall, UnaryCall } from '@protobuf-ts/runtime-rpc';
3
+ /**
4
+ * A {@link GrpcWebFetchTransport} that issues its requests through a caller-supplied `fetch` instead
5
+ * of the global one.
6
+ *
7
+ * Why: `@protobuf-ts/grpcweb-transport` calls `globalThis.fetch(...)` directly and exposes no option
8
+ * to inject a custom fetch. Datadog RUM (and similar agents) monkey-patch `window.fetch` and
9
+ * `clone()` + read every response to measure timing; on the long-lived grpc-web telemetry stream
10
+ * (`RegisterReceiver`) that clone-read can stall the client's read of the body. Reading the stream
11
+ * through a `fetch` captured before RUM patched the global avoids that.
12
+ *
13
+ * How: the parent transport invokes `globalThis.fetch(...)` **synchronously** while starting a call
14
+ * (it builds the request body synchronously, then calls fetch and returns the call object). We swap
15
+ * `globalThis.fetch` for that synchronous window only. JavaScript is single-threaded, so no other
16
+ * code — and therefore no other request — runs between the swap and the `finally` restore; the
17
+ * custom fetch cannot leak to unrelated requests. Reading the response happens later, but the
18
+ * `Response`/stream is already bound to the custom fetch by then.
19
+ *
20
+ * When `customFetch` is undefined this behaves exactly like {@link GrpcWebFetchTransport}.
21
+ */
22
+ export declare class FetchInjectingGrpcWebTransport extends GrpcWebFetchTransport {
23
+ private readonly customFetch;
24
+ constructor(customFetch: typeof globalThis.fetch | undefined, options: GrpcWebOptions);
25
+ private withFetch;
26
+ unary<I extends object, O extends object>(method: MethodInfo<I, O>, input: I, options: RpcOptions): UnaryCall<I, O>;
27
+ serverStreaming<I extends object, O extends object>(method: MethodInfo<I, O>, input: I, options: RpcOptions): ServerStreamingCall<I, O>;
28
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FetchInjectingGrpcWebTransport = void 0;
4
+ const grpcweb_transport_1 = require("@protobuf-ts/grpcweb-transport");
5
+ /**
6
+ * A {@link GrpcWebFetchTransport} that issues its requests through a caller-supplied `fetch` instead
7
+ * of the global one.
8
+ *
9
+ * Why: `@protobuf-ts/grpcweb-transport` calls `globalThis.fetch(...)` directly and exposes no option
10
+ * to inject a custom fetch. Datadog RUM (and similar agents) monkey-patch `window.fetch` and
11
+ * `clone()` + read every response to measure timing; on the long-lived grpc-web telemetry stream
12
+ * (`RegisterReceiver`) that clone-read can stall the client's read of the body. Reading the stream
13
+ * through a `fetch` captured before RUM patched the global avoids that.
14
+ *
15
+ * How: the parent transport invokes `globalThis.fetch(...)` **synchronously** while starting a call
16
+ * (it builds the request body synchronously, then calls fetch and returns the call object). We swap
17
+ * `globalThis.fetch` for that synchronous window only. JavaScript is single-threaded, so no other
18
+ * code — and therefore no other request — runs between the swap and the `finally` restore; the
19
+ * custom fetch cannot leak to unrelated requests. Reading the response happens later, but the
20
+ * `Response`/stream is already bound to the custom fetch by then.
21
+ *
22
+ * When `customFetch` is undefined this behaves exactly like {@link GrpcWebFetchTransport}.
23
+ */
24
+ class FetchInjectingGrpcWebTransport extends grpcweb_transport_1.GrpcWebFetchTransport {
25
+ constructor(customFetch, options) {
26
+ super(options);
27
+ this.customFetch = customFetch;
28
+ }
29
+ withFetch(start) {
30
+ if (!this.customFetch) {
31
+ return start();
32
+ }
33
+ const original = globalThis.fetch;
34
+ globalThis.fetch = this.customFetch;
35
+ try {
36
+ return start();
37
+ }
38
+ finally {
39
+ globalThis.fetch = original;
40
+ }
41
+ }
42
+ unary(method, input, options) {
43
+ return this.withFetch(() => super.unary(method, input, options));
44
+ }
45
+ serverStreaming(method, input, options) {
46
+ return this.withFetch(() => super.serverStreaming(method, input, options));
47
+ }
48
+ }
49
+ exports.FetchInjectingGrpcWebTransport = FetchInjectingGrpcWebTransport;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TelemetryStream = void 0;
4
4
  const models_1 = require("../../../models");
5
5
  const teletubby_pb_1 = require("../../../grpc/teletubby_pb");
6
- const grpcweb_transport_1 = require("@protobuf-ts/grpcweb-transport");
6
+ const FetchInjectingGrpcWebTransport_1 = require("./FetchInjectingGrpcWebTransport");
7
7
  const RocosLogger_1 = require("../../../logger/RocosLogger");
8
8
  const teletubby_pb_client_1 = require("../../../grpc/teletubby_pb.client");
9
9
  const TelemetryStreamAbstract_1 = require("./TelemetryStreamAbstract");
@@ -13,7 +13,11 @@ class TelemetryStream extends TelemetryStreamAbstract_1.TelemetryStreamAbstract
13
13
  this.logger = RocosLogger_1.RocosLogger.getInstance(`TelemetryStreamWeb(${this.identifier})`);
14
14
  const protocol = config.insecure ? 'http' : 'https';
15
15
  const port = config.port ? `:${config.port}` : '';
16
- const transport = new grpcweb_transport_1.GrpcWebFetchTransport({
16
+ // Optional caller-supplied fetch (e.g. captured before Datadog RUM patches window.fetch, whose
17
+ // response cloning otherwise stalls this long-lived stream). When omitted, the transport uses
18
+ // the global fetch. @protobuf-ts/grpcweb-transport has no fetch option, so the injection happens
19
+ // in FetchInjectingGrpcWebTransport.
20
+ const transport = new FetchInjectingGrpcWebTransport_1.FetchInjectingGrpcWebTransport(config.fetch, {
17
21
  baseUrl: `${protocol}://${this.url}${port}`,
18
22
  format: config.grpcWebFormat,
19
23
  });
@@ -30,6 +30,12 @@ class TelemetryStreamConnect extends TelemetryStreamAbstract_1.TelemetryStreamAb
30
30
  // payloads containing control characters (verified against a live robot on dev) and is
31
31
  // less efficient. Binary is required for correctness here.
32
32
  useBinaryFormat: true,
33
+ // Optional caller-supplied fetch. Pass a reference captured before page-level instrumentation
34
+ // (e.g. Datadog RUM) monkey-patches `window.fetch`: RUM clones and reads every fetch response
35
+ // to measure timing, and on this long-lived server-stream that clone-read stalls connect-web's
36
+ // read (stream connects 200 but yields no frames). When omitted, connect-web uses the global
37
+ // fetch. See docs/connect-rpc-investigation.md.
38
+ ...(config.fetch ? { fetch: config.fetch } : {}),
33
39
  });
34
40
  this.client = (0, connect_1.createClient)(teletubby_pb_1.TelemetryGateway, transport);
35
41
  }
@@ -12,6 +12,7 @@ interface ConfigBase {
12
12
  port?: number;
13
13
  insecure?: boolean;
14
14
  transport?: TelemetryTransport;
15
+ fetch?: typeof globalThis.fetch;
15
16
  hooks?: {
16
17
  http?: {
17
18
  onResponse?: (res: Response) => void;
@@ -9,4 +9,5 @@ export interface IStreamConfig {
9
9
  insecure?: boolean;
10
10
  transport?: TelemetryTransport;
11
11
  grpcWebFormat?: 'text' | 'binary';
12
+ fetch?: typeof globalThis.fetch;
12
13
  }
@@ -164,6 +164,7 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
164
164
  port: this.config.port,
165
165
  insecure: this.config.insecure,
166
166
  transport: this.config.transport,
167
+ fetch: this.config.fetch,
167
168
  });
168
169
  if (!newStream.isNew) {
169
170
  newStream.stream.statusStream$.subscribe((msg) => {
@@ -0,0 +1,28 @@
1
+ import { GrpcWebFetchTransport, GrpcWebOptions } from '@protobuf-ts/grpcweb-transport';
2
+ import type { MethodInfo, RpcOptions, ServerStreamingCall, UnaryCall } from '@protobuf-ts/runtime-rpc';
3
+ /**
4
+ * A {@link GrpcWebFetchTransport} that issues its requests through a caller-supplied `fetch` instead
5
+ * of the global one.
6
+ *
7
+ * Why: `@protobuf-ts/grpcweb-transport` calls `globalThis.fetch(...)` directly and exposes no option
8
+ * to inject a custom fetch. Datadog RUM (and similar agents) monkey-patch `window.fetch` and
9
+ * `clone()` + read every response to measure timing; on the long-lived grpc-web telemetry stream
10
+ * (`RegisterReceiver`) that clone-read can stall the client's read of the body. Reading the stream
11
+ * through a `fetch` captured before RUM patched the global avoids that.
12
+ *
13
+ * How: the parent transport invokes `globalThis.fetch(...)` **synchronously** while starting a call
14
+ * (it builds the request body synchronously, then calls fetch and returns the call object). We swap
15
+ * `globalThis.fetch` for that synchronous window only. JavaScript is single-threaded, so no other
16
+ * code — and therefore no other request — runs between the swap and the `finally` restore; the
17
+ * custom fetch cannot leak to unrelated requests. Reading the response happens later, but the
18
+ * `Response`/stream is already bound to the custom fetch by then.
19
+ *
20
+ * When `customFetch` is undefined this behaves exactly like {@link GrpcWebFetchTransport}.
21
+ */
22
+ export declare class FetchInjectingGrpcWebTransport extends GrpcWebFetchTransport {
23
+ private readonly customFetch;
24
+ constructor(customFetch: typeof globalThis.fetch | undefined, options: GrpcWebOptions);
25
+ private withFetch;
26
+ unary<I extends object, O extends object>(method: MethodInfo<I, O>, input: I, options: RpcOptions): UnaryCall<I, O>;
27
+ serverStreaming<I extends object, O extends object>(method: MethodInfo<I, O>, input: I, options: RpcOptions): ServerStreamingCall<I, O>;
28
+ }
@@ -0,0 +1,45 @@
1
+ import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport';
2
+ /**
3
+ * A {@link GrpcWebFetchTransport} that issues its requests through a caller-supplied `fetch` instead
4
+ * of the global one.
5
+ *
6
+ * Why: `@protobuf-ts/grpcweb-transport` calls `globalThis.fetch(...)` directly and exposes no option
7
+ * to inject a custom fetch. Datadog RUM (and similar agents) monkey-patch `window.fetch` and
8
+ * `clone()` + read every response to measure timing; on the long-lived grpc-web telemetry stream
9
+ * (`RegisterReceiver`) that clone-read can stall the client's read of the body. Reading the stream
10
+ * through a `fetch` captured before RUM patched the global avoids that.
11
+ *
12
+ * How: the parent transport invokes `globalThis.fetch(...)` **synchronously** while starting a call
13
+ * (it builds the request body synchronously, then calls fetch and returns the call object). We swap
14
+ * `globalThis.fetch` for that synchronous window only. JavaScript is single-threaded, so no other
15
+ * code — and therefore no other request — runs between the swap and the `finally` restore; the
16
+ * custom fetch cannot leak to unrelated requests. Reading the response happens later, but the
17
+ * `Response`/stream is already bound to the custom fetch by then.
18
+ *
19
+ * When `customFetch` is undefined this behaves exactly like {@link GrpcWebFetchTransport}.
20
+ */
21
+ export class FetchInjectingGrpcWebTransport extends GrpcWebFetchTransport {
22
+ constructor(customFetch, options) {
23
+ super(options);
24
+ this.customFetch = customFetch;
25
+ }
26
+ withFetch(start) {
27
+ if (!this.customFetch) {
28
+ return start();
29
+ }
30
+ const original = globalThis.fetch;
31
+ globalThis.fetch = this.customFetch;
32
+ try {
33
+ return start();
34
+ }
35
+ finally {
36
+ globalThis.fetch = original;
37
+ }
38
+ }
39
+ unary(method, input, options) {
40
+ return this.withFetch(() => super.unary(method, input, options));
41
+ }
42
+ serverStreaming(method, input, options) {
43
+ return this.withFetch(() => super.serverStreaming(method, input, options));
44
+ }
45
+ }
@@ -1,6 +1,6 @@
1
1
  import { SubscriberStatusEnum } from '../../../models';
2
2
  import { RegistrationMessage, } from '../../../grpc/teletubby_pb';
3
- import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport';
3
+ import { FetchInjectingGrpcWebTransport } from './FetchInjectingGrpcWebTransport';
4
4
  import { RocosLogger } from '../../../logger/RocosLogger';
5
5
  import { TelemetryGatewayClient } from '../../../grpc/teletubby_pb.client';
6
6
  import { TelemetryStreamAbstract } from './TelemetryStreamAbstract';
@@ -10,7 +10,11 @@ export class TelemetryStream extends TelemetryStreamAbstract {
10
10
  this.logger = RocosLogger.getInstance(`TelemetryStreamWeb(${this.identifier})`);
11
11
  const protocol = config.insecure ? 'http' : 'https';
12
12
  const port = config.port ? `:${config.port}` : '';
13
- const transport = new GrpcWebFetchTransport({
13
+ // Optional caller-supplied fetch (e.g. captured before Datadog RUM patches window.fetch, whose
14
+ // response cloning otherwise stalls this long-lived stream). When omitted, the transport uses
15
+ // the global fetch. @protobuf-ts/grpcweb-transport has no fetch option, so the injection happens
16
+ // in FetchInjectingGrpcWebTransport.
17
+ const transport = new FetchInjectingGrpcWebTransport(config.fetch, {
14
18
  baseUrl: `${protocol}://${this.url}${port}`,
15
19
  format: config.grpcWebFormat,
16
20
  });
@@ -27,6 +27,12 @@ export class TelemetryStreamConnect extends TelemetryStreamAbstract {
27
27
  // payloads containing control characters (verified against a live robot on dev) and is
28
28
  // less efficient. Binary is required for correctness here.
29
29
  useBinaryFormat: true,
30
+ // Optional caller-supplied fetch. Pass a reference captured before page-level instrumentation
31
+ // (e.g. Datadog RUM) monkey-patches `window.fetch`: RUM clones and reads every fetch response
32
+ // to measure timing, and on this long-lived server-stream that clone-read stalls connect-web's
33
+ // read (stream connects 200 but yields no frames). When omitted, connect-web uses the global
34
+ // fetch. See docs/connect-rpc-investigation.md.
35
+ ...(config.fetch ? { fetch: config.fetch } : {}),
30
36
  });
31
37
  this.client = createClient(TelemetryGateway, transport);
32
38
  }
@@ -12,6 +12,7 @@ interface ConfigBase {
12
12
  port?: number;
13
13
  insecure?: boolean;
14
14
  transport?: TelemetryTransport;
15
+ fetch?: typeof globalThis.fetch;
15
16
  hooks?: {
16
17
  http?: {
17
18
  onResponse?: (res: Response) => void;
@@ -9,4 +9,5 @@ export interface IStreamConfig {
9
9
  insecure?: boolean;
10
10
  transport?: TelemetryTransport;
11
11
  grpcWebFormat?: 'text' | 'binary';
12
+ fetch?: typeof globalThis.fetch;
12
13
  }
@@ -161,6 +161,7 @@ export class TelemetryService extends BaseStreamService {
161
161
  port: this.config.port,
162
162
  insecure: this.config.insecure,
163
163
  transport: this.config.transport,
164
+ fetch: this.config.fetch,
164
165
  });
165
166
  if (!newStream.isNew) {
166
167
  newStream.stream.statusStream$.subscribe((msg) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dronedeploy/rocos-js-sdk",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
4
4
  "description": "Javascript SDK for rocos",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",