@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.
- package/cjs/api/streams/telemetry/FetchInjectingGrpcWebTransport.d.ts +28 -0
- package/cjs/api/streams/telemetry/FetchInjectingGrpcWebTransport.js +49 -0
- package/cjs/api/streams/telemetry/TelemetryStream.js +6 -2
- package/cjs/api/streams/telemetry/TelemetryStreamConnect.js +6 -0
- package/cjs/models/IRocosSDKConfig.d.ts +1 -0
- package/cjs/models/IStreamConfig.d.ts +1 -0
- package/cjs/services/TelemetryService.js +1 -0
- package/esm/api/streams/telemetry/FetchInjectingGrpcWebTransport.d.ts +28 -0
- package/esm/api/streams/telemetry/FetchInjectingGrpcWebTransport.js +45 -0
- package/esm/api/streams/telemetry/TelemetryStream.js +6 -2
- package/esm/api/streams/telemetry/TelemetryStreamConnect.js +6 -0
- package/esm/models/IRocosSDKConfig.d.ts +1 -0
- package/esm/models/IStreamConfig.d.ts +1 -0
- package/esm/services/TelemetryService.js +1 -0
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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 {
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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) => {
|