@loadstrike/loadstrike-sdk 1.0.31601 → 1.0.32601
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 +14 -2
- package/dist/cjs/internal/prometheus-remote-write.js +37 -0
- package/dist/cjs/internal/reporting-sink-http-error.js +17 -0
- package/dist/cjs/internal/vendor-metric-payloads.js +390 -0
- package/dist/cjs/iteration-observations.js +24 -8
- package/dist/cjs/local.js +48 -63
- package/dist/cjs/reporting-containment.js +242 -0
- package/dist/cjs/runtime.js +83 -3
- package/dist/cjs/sinks.js +1337 -38
- package/dist/cjs/transports.js +1339 -151
- package/dist/esm/internal/prometheus-remote-write.js +31 -0
- package/dist/esm/internal/reporting-sink-http-error.js +13 -0
- package/dist/esm/internal/vendor-metric-payloads.js +382 -0
- package/dist/esm/iteration-observations.js +24 -8
- package/dist/esm/local.js +49 -64
- package/dist/esm/reporting-containment.js +238 -0
- package/dist/esm/runtime.js +85 -5
- package/dist/esm/sinks.js +1334 -35
- package/dist/esm/transports.js +1335 -151
- package/dist/types/contracts.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/internal/prometheus-remote-write.d.ts +2 -0
- package/dist/types/internal/reporting-sink-http-error.d.ts +6 -0
- package/dist/types/internal/vendor-metric-payloads.d.ts +48 -0
- package/dist/types/local.d.ts +0 -6
- package/dist/types/reporting-containment.d.ts +2 -0
- package/dist/types/runtime.d.ts +1 -0
- package/dist/types/sinks.d.ts +134 -17
- package/dist/types/transports.d.ts +2 -0
- package/package.json +9 -3
- package/dist/cjs/internal-build.js +0 -4
- package/dist/esm/internal-build.js +0 -1
- package/dist/types/internal-build.d.ts +0 -1
package/dist/cjs/transports.js
CHANGED
|
@@ -32,12 +32,18 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.__loadstrikeTestExports = exports.EndpointAdapterFactory = exports.KafkaSaslOptions = exports.HttpAuthOptions = exports.HttpOAuth2ClientCredentialsOptions = exports.WebSocketEndpointDefinition = exports.GrpcEndpointDefinition = exports.PushDiffusionEndpointDefinition = exports.DelegateStreamEndpointDefinition = exports.SqsEndpointDefinition = exports.AzureEventHubsEndpointDefinition = exports.RedisStreamsEndpointDefinition = exports.NatsEndpointDefinition = exports.RabbitMqEndpointDefinition = exports.KafkaEndpointDefinition = exports.HttpEndpointDefinition = exports.TrafficEndpointDefinition = exports.WebSocketNativeClientOptions = exports.WebSocketExpectedMessage = exports.WebSocketMessageSpec = exports.WebSocketReconnectPolicy = exports.ProtocolMetricSnapshot = exports.GrpcStatusMapper = exports.GrpcNativeClientOptions = exports.GrpcMethodTypes = exports.LOADSTRIKE_TRACE_ID_TRACKING_FIELD = exports.LOADSTRIKE_TRACE_ID_HEADER = void 0;
|
|
40
|
+
exports.validateNativeEndpointExecutionSupport = validateNativeEndpointExecutionSupport;
|
|
37
41
|
const node_crypto_1 = require("node:crypto");
|
|
42
|
+
const ws_1 = __importDefault(require("ws"));
|
|
38
43
|
const correlation_js_1 = require("./correlation.js");
|
|
39
44
|
exports.LOADSTRIKE_TRACE_ID_HEADER = "loadstrike-trace-id";
|
|
40
45
|
exports.LOADSTRIKE_TRACE_ID_TRACKING_FIELD = `header:${exports.LOADSTRIKE_TRACE_ID_HEADER}`;
|
|
46
|
+
const NATIVE_GRPC_EXECUTION_UNAVAILABLE_MESSAGE = "Native gRPC execution is not available in this SDK version. Provide the endpoint Produce/Consume delegate instead.";
|
|
41
47
|
class TrafficEndpointDefinitionModel {
|
|
42
48
|
get JsonSerializerSettings() {
|
|
43
49
|
return this.JsonSettings;
|
|
@@ -459,6 +465,15 @@ class GrpcEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
|
459
465
|
}
|
|
460
466
|
Validate() {
|
|
461
467
|
super.Validate();
|
|
468
|
+
const hasModeAsyncDelegate = this.Mode === "Produce"
|
|
469
|
+
? typeof this.ProduceAsync === "function"
|
|
470
|
+
: typeof this.ConsumeAsync === "function";
|
|
471
|
+
const hasModeDelegate = this.Mode === "Produce"
|
|
472
|
+
? typeof this.Produce === "function" || hasModeAsyncDelegate
|
|
473
|
+
: typeof this.Consume === "function" || hasModeAsyncDelegate;
|
|
474
|
+
if (this.NativeClient && !hasModeDelegate) {
|
|
475
|
+
throw new Error(NATIVE_GRPC_EXECUTION_UNAVAILABLE_MESSAGE);
|
|
476
|
+
}
|
|
462
477
|
requireNonEmptyString(this.Target, "Target must be provided for gRPC endpoint definitions.");
|
|
463
478
|
requireNonEmptyString(this.ServiceName, "ServiceName must be provided for gRPC endpoint definitions.");
|
|
464
479
|
requireNonEmptyString(this.MethodName, "MethodName must be provided for gRPC endpoint definitions.");
|
|
@@ -466,40 +481,177 @@ class GrpcEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
|
466
481
|
if (this.DeadlineSeconds <= 0) {
|
|
467
482
|
throw new RangeError("Deadline must be greater than zero.");
|
|
468
483
|
}
|
|
469
|
-
this.NativeClient
|
|
470
|
-
if (this.Mode === "Produce" && typeof this.ProduceAsync !== "function" && !this.NativeClient) {
|
|
484
|
+
if (this.Mode === "Produce" && !hasModeDelegate && !this.NativeClient) {
|
|
471
485
|
throw new Error("ProduceAsync delegate must be provided when endpoint mode is Produce.");
|
|
472
486
|
}
|
|
473
|
-
if (this.Mode === "Consume" &&
|
|
487
|
+
if (this.Mode === "Consume" && !hasModeDelegate && !this.NativeClient) {
|
|
474
488
|
throw new Error("ConsumeAsync delegate must be provided when endpoint mode is Consume.");
|
|
475
489
|
}
|
|
476
490
|
}
|
|
477
491
|
}
|
|
492
|
+
const webSocketReconnectValidationInputs = new WeakMap();
|
|
493
|
+
const webSocketMessageValidationInputs = new WeakMap();
|
|
494
|
+
const webSocketExpectedValidationInputs = new WeakMap();
|
|
495
|
+
const webSocketNativeValidationInputs = new WeakMap();
|
|
496
|
+
const NATIVE_WEBSOCKET_RECONNECT_INPUT_FIELDS = [
|
|
497
|
+
["MaxAttempts", "maxAttempts"],
|
|
498
|
+
["DelayMs", "delayMs"],
|
|
499
|
+
["DelaySeconds", "delaySeconds", "Delay", "delay"]
|
|
500
|
+
];
|
|
501
|
+
const NATIVE_WEBSOCKET_MESSAGE_INPUT_FIELDS = [
|
|
502
|
+
["Kind", "kind"],
|
|
503
|
+
["TextPayload", "textPayload"],
|
|
504
|
+
["BinaryPayload", "binaryPayload"]
|
|
505
|
+
];
|
|
506
|
+
const NATIVE_WEBSOCKET_EXPECTED_INPUT_FIELDS = [
|
|
507
|
+
["ContainsText", "containsText"],
|
|
508
|
+
["ContainsBytes", "containsBytes"],
|
|
509
|
+
["TimeoutMs", "timeoutMs"],
|
|
510
|
+
["TimeoutSeconds", "timeoutSeconds", "Timeout", "timeout"]
|
|
511
|
+
];
|
|
512
|
+
const NATIVE_WEBSOCKET_CLIENT_INPUT_FIELDS = [
|
|
513
|
+
["Headers", "headers"],
|
|
514
|
+
["Cookies", "cookies"],
|
|
515
|
+
["PingIntervalMs", "pingIntervalMs"],
|
|
516
|
+
["PingIntervalSeconds", "pingIntervalSeconds", "PingInterval", "pingInterval"],
|
|
517
|
+
["Reconnect", "reconnect"],
|
|
518
|
+
["Messages", "messages"],
|
|
519
|
+
["ExpectedMessages", "expectedMessages"]
|
|
520
|
+
];
|
|
521
|
+
function captureSelectedNativeWebSocketFields(source, fieldGroups) {
|
|
522
|
+
const captured = {};
|
|
523
|
+
for (const fieldGroup of fieldGroups) {
|
|
524
|
+
const canonicalName = fieldGroup[0];
|
|
525
|
+
for (const fieldName of fieldGroup) {
|
|
526
|
+
if (fieldName in source) {
|
|
527
|
+
captured[canonicalName] = source[fieldName];
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return captured;
|
|
533
|
+
}
|
|
534
|
+
function captureNativeWebSocketRecordEntries(value) {
|
|
535
|
+
if (!isRecord(value)) {
|
|
536
|
+
return value;
|
|
537
|
+
}
|
|
538
|
+
const captured = {};
|
|
539
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
540
|
+
captured[key] = entry;
|
|
541
|
+
}
|
|
542
|
+
return captured;
|
|
543
|
+
}
|
|
544
|
+
function captureNativeWebSocketReconnectInput(source) {
|
|
545
|
+
return captureSelectedNativeWebSocketFields(source, NATIVE_WEBSOCKET_RECONNECT_INPUT_FIELDS);
|
|
546
|
+
}
|
|
547
|
+
function captureNativeWebSocketMessageInput(source) {
|
|
548
|
+
const captured = captureSelectedNativeWebSocketFields(source, NATIVE_WEBSOCKET_MESSAGE_INPUT_FIELDS);
|
|
549
|
+
if (Array.isArray(captured.BinaryPayload)) {
|
|
550
|
+
captured.BinaryPayload = Array.from(captured.BinaryPayload);
|
|
551
|
+
}
|
|
552
|
+
return captured;
|
|
553
|
+
}
|
|
554
|
+
function captureNativeWebSocketExpectedInput(source) {
|
|
555
|
+
const captured = captureSelectedNativeWebSocketFields(source, NATIVE_WEBSOCKET_EXPECTED_INPUT_FIELDS);
|
|
556
|
+
if (Array.isArray(captured.ContainsBytes)) {
|
|
557
|
+
captured.ContainsBytes = Array.from(captured.ContainsBytes);
|
|
558
|
+
}
|
|
559
|
+
return captured;
|
|
560
|
+
}
|
|
561
|
+
function captureNativeWebSocketOptionsInput(source) {
|
|
562
|
+
const captured = captureSelectedNativeWebSocketFields(source, NATIVE_WEBSOCKET_CLIENT_INPUT_FIELDS);
|
|
563
|
+
if ("Headers" in captured) {
|
|
564
|
+
captured.Headers = captureNativeWebSocketRecordEntries(captured.Headers);
|
|
565
|
+
}
|
|
566
|
+
if (Array.isArray(captured.Cookies)) {
|
|
567
|
+
captured.Cookies = Array.from(captured.Cookies);
|
|
568
|
+
}
|
|
569
|
+
if (isRecord(captured.Reconnect) && !(captured.Reconnect instanceof WebSocketReconnectPolicy)) {
|
|
570
|
+
captured.Reconnect = captureNativeWebSocketReconnectInput(captured.Reconnect);
|
|
571
|
+
}
|
|
572
|
+
if (Array.isArray(captured.Messages)) {
|
|
573
|
+
captured.Messages = captured.Messages.map((entry) => (entry instanceof WebSocketMessageSpec || !isRecord(entry)
|
|
574
|
+
? entry
|
|
575
|
+
: captureNativeWebSocketMessageInput(entry)));
|
|
576
|
+
}
|
|
577
|
+
if (Array.isArray(captured.ExpectedMessages)) {
|
|
578
|
+
captured.ExpectedMessages = captured.ExpectedMessages.map((entry) => (entry instanceof WebSocketExpectedMessage || !isRecord(entry)
|
|
579
|
+
? entry
|
|
580
|
+
: captureNativeWebSocketExpectedInput(entry)));
|
|
581
|
+
}
|
|
582
|
+
return captured;
|
|
583
|
+
}
|
|
584
|
+
function snapshotNativeWebSocketInput(value, seen = new WeakMap()) {
|
|
585
|
+
if (value == null || typeof value !== "object") {
|
|
586
|
+
return value;
|
|
587
|
+
}
|
|
588
|
+
if (value instanceof WebSocketReconnectPolicy
|
|
589
|
+
|| value instanceof WebSocketMessageSpec
|
|
590
|
+
|| value instanceof WebSocketExpectedMessage) {
|
|
591
|
+
return value;
|
|
592
|
+
}
|
|
593
|
+
const existing = seen.get(value);
|
|
594
|
+
if (existing !== undefined) {
|
|
595
|
+
return existing;
|
|
596
|
+
}
|
|
597
|
+
if (value instanceof Uint8Array) {
|
|
598
|
+
return new Uint8Array(value);
|
|
599
|
+
}
|
|
600
|
+
if (value instanceof ArrayBuffer) {
|
|
601
|
+
return value.slice(0);
|
|
602
|
+
}
|
|
603
|
+
if (Array.isArray(value)) {
|
|
604
|
+
const clone = [];
|
|
605
|
+
seen.set(value, clone);
|
|
606
|
+
for (const entry of value) {
|
|
607
|
+
clone.push(snapshotNativeWebSocketInput(entry, seen));
|
|
608
|
+
}
|
|
609
|
+
return clone;
|
|
610
|
+
}
|
|
611
|
+
const clone = {};
|
|
612
|
+
seen.set(value, clone);
|
|
613
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
614
|
+
clone[key] = snapshotNativeWebSocketInput(entry, seen);
|
|
615
|
+
}
|
|
616
|
+
return clone;
|
|
617
|
+
}
|
|
618
|
+
function defineSynchronizedPublicAlias(target, alias, read, write) {
|
|
619
|
+
Object.defineProperty(target, alias, {
|
|
620
|
+
configurable: true,
|
|
621
|
+
enumerable: true,
|
|
622
|
+
get: read,
|
|
623
|
+
set: write
|
|
624
|
+
});
|
|
625
|
+
}
|
|
478
626
|
class WebSocketReconnectPolicy {
|
|
479
627
|
constructor(initial = {}) {
|
|
480
628
|
this.MaxAttempts = 0;
|
|
481
629
|
this.maxAttempts = 0;
|
|
482
630
|
this.DelaySeconds = 1;
|
|
483
631
|
this.delaySeconds = 1;
|
|
484
|
-
|
|
485
|
-
|
|
632
|
+
defineSynchronizedPublicAlias(this, "maxAttempts", () => this.MaxAttempts, (value) => {
|
|
633
|
+
this.MaxAttempts = value;
|
|
634
|
+
});
|
|
635
|
+
defineSynchronizedPublicAlias(this, "delaySeconds", () => this.DelaySeconds, (value) => {
|
|
636
|
+
this.DelaySeconds = value;
|
|
637
|
+
});
|
|
638
|
+
const raw = captureNativeWebSocketReconnectInput(asRecordOrEmpty(initial));
|
|
639
|
+
webSocketReconnectValidationInputs.set(this, snapshotNativeWebSocketInput(raw));
|
|
640
|
+
this.MaxAttempts = (pickEndpointValue(raw, "MaxAttempts", "maxAttempts") ?? 0);
|
|
486
641
|
this.maxAttempts = this.MaxAttempts;
|
|
487
|
-
const delayMs =
|
|
642
|
+
const delayMs = pickEndpointValue(raw, "DelayMs", "delayMs");
|
|
643
|
+
const delaySeconds = pickEndpointValue(raw, "DelaySeconds", "delaySeconds", "Delay", "delay");
|
|
488
644
|
this.DelaySeconds = delayMs != null
|
|
489
645
|
? delayMs / 1000
|
|
490
|
-
: (
|
|
646
|
+
: (delaySeconds ?? 1);
|
|
491
647
|
this.delaySeconds = this.DelaySeconds;
|
|
492
648
|
}
|
|
493
649
|
Validate() {
|
|
494
650
|
this.validate();
|
|
495
651
|
}
|
|
496
652
|
validate() {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
500
|
-
if (this.DelaySeconds < 0) {
|
|
501
|
-
throw new RangeError("Delay cannot be negative.");
|
|
502
|
-
}
|
|
653
|
+
validateNativeWebSocketReconnectPolicy(webSocketReconnectValidationInputs.get(this) ?? {});
|
|
654
|
+
validateNativeWebSocketReconnectPolicy(this);
|
|
503
655
|
}
|
|
504
656
|
}
|
|
505
657
|
exports.WebSocketReconnectPolicy = WebSocketReconnectPolicy;
|
|
@@ -507,17 +659,27 @@ class WebSocketMessageSpec {
|
|
|
507
659
|
constructor(initial = {}) {
|
|
508
660
|
this.Kind = "Text";
|
|
509
661
|
this.kind = "Text";
|
|
510
|
-
|
|
662
|
+
defineSynchronizedPublicAlias(this, "kind", () => this.Kind, (value) => {
|
|
663
|
+
this.Kind = value;
|
|
664
|
+
});
|
|
665
|
+
defineSynchronizedPublicAlias(this, "textPayload", () => this.TextPayload, (value) => {
|
|
666
|
+
this.TextPayload = value;
|
|
667
|
+
});
|
|
668
|
+
defineSynchronizedPublicAlias(this, "binaryPayload", () => this.BinaryPayload, (value) => {
|
|
669
|
+
this.BinaryPayload = value;
|
|
670
|
+
});
|
|
671
|
+
const raw = captureNativeWebSocketMessageInput(asRecordOrEmpty(initial));
|
|
672
|
+
webSocketMessageValidationInputs.set(this, snapshotNativeWebSocketInput(raw));
|
|
511
673
|
this.Kind = pickOptionalEndpointString(raw, "Kind", "kind") ?? "Text";
|
|
512
674
|
this.kind = this.Kind;
|
|
513
|
-
this.TextPayload =
|
|
675
|
+
this.TextPayload = pickOptionalEndpointExactString(raw, "TextPayload", "textPayload");
|
|
514
676
|
this.textPayload = this.TextPayload;
|
|
515
677
|
const binary = pickEndpointValue(raw, "BinaryPayload", "binaryPayload");
|
|
516
678
|
if (binary instanceof Uint8Array) {
|
|
517
679
|
this.BinaryPayload = binary;
|
|
518
680
|
}
|
|
519
681
|
else if (Array.isArray(binary)) {
|
|
520
|
-
this.BinaryPayload = Uint8Array.from(binary.map((value) => Number(value)
|
|
682
|
+
this.BinaryPayload = Uint8Array.from(binary.map((value) => Number(value)));
|
|
521
683
|
}
|
|
522
684
|
this.binaryPayload = this.BinaryPayload;
|
|
523
685
|
}
|
|
@@ -531,12 +693,8 @@ class WebSocketMessageSpec {
|
|
|
531
693
|
this.validate();
|
|
532
694
|
}
|
|
533
695
|
validate() {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
if (this.Kind.toLowerCase() === "binary" && this.BinaryPayload == null) {
|
|
538
|
-
throw new Error("Binary WebSocket messages require BinaryPayload.");
|
|
539
|
-
}
|
|
696
|
+
validateOriginalNativeWebSocketMessage(webSocketMessageValidationInputs.get(this) ?? {});
|
|
697
|
+
validateNativeWebSocketMessage(this);
|
|
540
698
|
}
|
|
541
699
|
}
|
|
542
700
|
exports.WebSocketMessageSpec = WebSocketMessageSpec;
|
|
@@ -544,30 +702,40 @@ class WebSocketExpectedMessage {
|
|
|
544
702
|
constructor(initial = {}) {
|
|
545
703
|
this.TimeoutSeconds = 30;
|
|
546
704
|
this.timeoutSeconds = 30;
|
|
547
|
-
|
|
548
|
-
|
|
705
|
+
defineSynchronizedPublicAlias(this, "containsText", () => this.ContainsText, (value) => {
|
|
706
|
+
this.ContainsText = value;
|
|
707
|
+
});
|
|
708
|
+
defineSynchronizedPublicAlias(this, "containsBytes", () => this.ContainsBytes, (value) => {
|
|
709
|
+
this.ContainsBytes = value;
|
|
710
|
+
});
|
|
711
|
+
defineSynchronizedPublicAlias(this, "timeoutSeconds", () => this.TimeoutSeconds, (value) => {
|
|
712
|
+
this.TimeoutSeconds = value;
|
|
713
|
+
});
|
|
714
|
+
const raw = captureNativeWebSocketExpectedInput(asRecordOrEmpty(initial));
|
|
715
|
+
webSocketExpectedValidationInputs.set(this, snapshotNativeWebSocketInput(raw));
|
|
716
|
+
this.ContainsText = pickOptionalEndpointExactString(raw, "ContainsText", "containsText");
|
|
549
717
|
this.containsText = this.ContainsText;
|
|
550
718
|
const bytes = pickEndpointValue(raw, "ContainsBytes", "containsBytes");
|
|
551
719
|
if (bytes instanceof Uint8Array) {
|
|
552
720
|
this.ContainsBytes = bytes;
|
|
553
721
|
}
|
|
554
722
|
else if (Array.isArray(bytes)) {
|
|
555
|
-
this.ContainsBytes = Uint8Array.from(bytes.map((value) => Number(value)
|
|
723
|
+
this.ContainsBytes = Uint8Array.from(bytes.map((value) => Number(value)));
|
|
556
724
|
}
|
|
557
725
|
this.containsBytes = this.ContainsBytes;
|
|
558
|
-
const timeoutMs =
|
|
726
|
+
const timeoutMs = pickEndpointValue(raw, "TimeoutMs", "timeoutMs");
|
|
727
|
+
const timeoutSeconds = pickEndpointValue(raw, "TimeoutSeconds", "timeoutSeconds", "Timeout", "timeout");
|
|
559
728
|
this.TimeoutSeconds = timeoutMs != null
|
|
560
729
|
? timeoutMs / 1000
|
|
561
|
-
: (
|
|
730
|
+
: (timeoutSeconds ?? 30);
|
|
562
731
|
this.timeoutSeconds = this.TimeoutSeconds;
|
|
563
732
|
}
|
|
564
733
|
Validate() {
|
|
565
734
|
this.validate();
|
|
566
735
|
}
|
|
567
736
|
validate() {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
737
|
+
validateNativeWebSocketExpectedMessage(webSocketExpectedValidationInputs.get(this) ?? {});
|
|
738
|
+
validateNativeWebSocketExpectedMessage(this);
|
|
571
739
|
}
|
|
572
740
|
}
|
|
573
741
|
exports.WebSocketExpectedMessage = WebSocketExpectedMessage;
|
|
@@ -583,13 +751,32 @@ class WebSocketNativeClientOptions {
|
|
|
583
751
|
this.messages = [];
|
|
584
752
|
this.ExpectedMessages = [];
|
|
585
753
|
this.expectedMessages = [];
|
|
586
|
-
|
|
754
|
+
defineSynchronizedPublicAlias(this, "headers", () => this.Headers, (value) => {
|
|
755
|
+
this.Headers = value;
|
|
756
|
+
});
|
|
757
|
+
defineSynchronizedPublicAlias(this, "cookies", () => this.Cookies, (value) => {
|
|
758
|
+
this.Cookies = value;
|
|
759
|
+
});
|
|
760
|
+
defineSynchronizedPublicAlias(this, "pingIntervalSeconds", () => this.PingIntervalSeconds, (value) => {
|
|
761
|
+
this.PingIntervalSeconds = value;
|
|
762
|
+
});
|
|
763
|
+
defineSynchronizedPublicAlias(this, "reconnect", () => this.Reconnect, (value) => {
|
|
764
|
+
this.Reconnect = value;
|
|
765
|
+
});
|
|
766
|
+
defineSynchronizedPublicAlias(this, "messages", () => this.Messages, (value) => {
|
|
767
|
+
this.Messages = value;
|
|
768
|
+
});
|
|
769
|
+
defineSynchronizedPublicAlias(this, "expectedMessages", () => this.ExpectedMessages, (value) => {
|
|
770
|
+
this.ExpectedMessages = value;
|
|
771
|
+
});
|
|
772
|
+
const raw = captureNativeWebSocketOptionsInput(asRecordOrEmpty(initial));
|
|
773
|
+
webSocketNativeValidationInputs.set(this, snapshotNativeWebSocketInput(raw));
|
|
587
774
|
this.Headers = pickEndpointStringRecord(raw, "Headers", "headers");
|
|
588
775
|
this.headers = this.Headers;
|
|
589
776
|
this.Cookies = pickEndpointStringArray(raw, "Cookies", "cookies") ?? [];
|
|
590
777
|
this.cookies = this.Cookies;
|
|
591
|
-
const pingMs =
|
|
592
|
-
const pingSeconds =
|
|
778
|
+
const pingMs = pickEndpointValue(raw, "PingIntervalMs", "pingIntervalMs");
|
|
779
|
+
const pingSeconds = pickEndpointValue(raw, "PingIntervalSeconds", "pingIntervalSeconds", "PingInterval", "pingInterval");
|
|
593
780
|
this.PingIntervalSeconds = pingMs != null ? pingMs / 1000 : pingSeconds;
|
|
594
781
|
this.pingIntervalSeconds = this.PingIntervalSeconds;
|
|
595
782
|
const reconnect = pickEndpointValue(raw, "Reconnect", "reconnect");
|
|
@@ -606,16 +793,8 @@ class WebSocketNativeClientOptions {
|
|
|
606
793
|
this.validate();
|
|
607
794
|
}
|
|
608
795
|
validate() {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
this.Reconnect.validate();
|
|
613
|
-
for (const message of this.Messages) {
|
|
614
|
-
message.validate();
|
|
615
|
-
}
|
|
616
|
-
for (const expected of this.ExpectedMessages) {
|
|
617
|
-
expected.validate();
|
|
618
|
-
}
|
|
796
|
+
validateNativeWebSocketOptions(webSocketNativeValidationInputs.get(this) ?? {});
|
|
797
|
+
validateNativeWebSocketOptions(this);
|
|
619
798
|
}
|
|
620
799
|
}
|
|
621
800
|
exports.WebSocketNativeClientOptions = WebSocketNativeClientOptions;
|
|
@@ -632,6 +811,23 @@ class WebSocketEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
|
632
811
|
}
|
|
633
812
|
Validate() {
|
|
634
813
|
super.Validate();
|
|
814
|
+
const hasModeAsyncDelegate = this.Mode === "Produce"
|
|
815
|
+
? typeof this.ProduceAsync === "function"
|
|
816
|
+
: typeof this.ConsumeAsync === "function";
|
|
817
|
+
const hasModeDelegate = this.Mode === "Produce"
|
|
818
|
+
? typeof this.Produce === "function" || hasModeAsyncDelegate
|
|
819
|
+
: typeof this.Consume === "function" || hasModeAsyncDelegate;
|
|
820
|
+
if (this.NativeClient != null && !hasModeDelegate) {
|
|
821
|
+
if (this.Mode === "Consume" && this.TrackingField.trim().toLowerCase().startsWith("header:")) {
|
|
822
|
+
throw new Error("Native WebSocket Consume cannot use a header TrackingField because WebSocket messages do not contain HTTP response headers.");
|
|
823
|
+
}
|
|
824
|
+
if (this.Mode === "Consume" && this.GatherByField?.trim().toLowerCase().startsWith("header:")) {
|
|
825
|
+
throw new Error("Native WebSocket Consume cannot use a header GatherByField because WebSocket messages do not contain HTTP response headers.");
|
|
826
|
+
}
|
|
827
|
+
validateNativeWebSocketSubprotocols(this.Subprotocols);
|
|
828
|
+
validateNativeWebSocketHeaderLayer(this.ConnectionMetadata, "ConnectionMetadata");
|
|
829
|
+
validateNativeWebSocketOptionsWithInstance(this.NativeClient);
|
|
830
|
+
}
|
|
635
831
|
const url = requireNonEmptyString(this.Url, "Url must be provided for WebSocket endpoint definitions.");
|
|
636
832
|
let parsed;
|
|
637
833
|
try {
|
|
@@ -640,20 +836,17 @@ class WebSocketEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
|
640
836
|
catch {
|
|
641
837
|
throw new Error("Url must be an absolute WebSocket URI using the ws or wss scheme.");
|
|
642
838
|
}
|
|
643
|
-
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:")
|
|
839
|
+
if ((parsed.protocol !== "ws:" && parsed.protocol !== "wss:")
|
|
840
|
+
|| !parsed.hostname
|
|
841
|
+
|| Boolean(parsed.hash)) {
|
|
644
842
|
throw new Error("Url must be an absolute WebSocket URI using the ws or wss scheme.");
|
|
645
843
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if (this.CloseTimeoutSeconds <= 0) {
|
|
650
|
-
throw new RangeError("CloseTimeout must be greater than zero.");
|
|
651
|
-
}
|
|
652
|
-
this.NativeClient?.validate();
|
|
653
|
-
if (this.Mode === "Produce" && typeof this.ProduceAsync !== "function" && !this.NativeClient) {
|
|
844
|
+
validateNativeWebSocketTimerDelay(undefined, this.ConnectTimeoutSeconds, "ConnectTimeout");
|
|
845
|
+
validateNativeWebSocketTimerDelay(undefined, this.CloseTimeoutSeconds, "CloseTimeout");
|
|
846
|
+
if (this.Mode === "Produce" && !hasModeDelegate && this.NativeClient == null) {
|
|
654
847
|
throw new Error("ProduceAsync delegate must be provided when endpoint mode is Produce.");
|
|
655
848
|
}
|
|
656
|
-
if (this.Mode === "Consume" &&
|
|
849
|
+
if (this.Mode === "Consume" && !hasModeDelegate && this.NativeClient == null) {
|
|
657
850
|
throw new Error("ConsumeAsync delegate must be provided when endpoint mode is Consume.");
|
|
658
851
|
}
|
|
659
852
|
}
|
|
@@ -1180,25 +1373,31 @@ function initializeWebSocketEndpointDefinitionModel(target, initial) {
|
|
|
1180
1373
|
if (url) {
|
|
1181
1374
|
target.Url = url;
|
|
1182
1375
|
}
|
|
1183
|
-
const subprotocols =
|
|
1184
|
-
if (subprotocols) {
|
|
1185
|
-
target.Subprotocols = subprotocols;
|
|
1376
|
+
const subprotocols = pickEndpointValue(raw, "Subprotocols", "subprotocols");
|
|
1377
|
+
if (subprotocols != null) {
|
|
1378
|
+
target.Subprotocols = normalizeWebSocketEndpointStringArrayInput(subprotocols);
|
|
1186
1379
|
}
|
|
1187
|
-
const connectMs =
|
|
1188
|
-
const connectSeconds =
|
|
1380
|
+
const connectMs = pickEndpointValue(raw, "ConnectTimeoutMs", "connectTimeoutMs");
|
|
1381
|
+
const connectSeconds = pickEndpointValue(raw, "ConnectTimeoutSeconds", "connectTimeoutSeconds", "ConnectTimeout", "connectTimeout");
|
|
1189
1382
|
if (connectMs != null) {
|
|
1190
|
-
|
|
1383
|
+
const normalized = normalizeWebSocketEndpointNumberInput(connectMs);
|
|
1384
|
+
target.ConnectTimeoutSeconds = typeof normalized === "number"
|
|
1385
|
+
? normalized / 1000
|
|
1386
|
+
: normalized;
|
|
1191
1387
|
}
|
|
1192
1388
|
else if (connectSeconds != null) {
|
|
1193
|
-
target.ConnectTimeoutSeconds = connectSeconds;
|
|
1389
|
+
target.ConnectTimeoutSeconds = normalizeWebSocketEndpointNumberInput(connectSeconds);
|
|
1194
1390
|
}
|
|
1195
|
-
const closeMs =
|
|
1196
|
-
const closeSeconds =
|
|
1391
|
+
const closeMs = pickEndpointValue(raw, "CloseTimeoutMs", "closeTimeoutMs");
|
|
1392
|
+
const closeSeconds = pickEndpointValue(raw, "CloseTimeoutSeconds", "closeTimeoutSeconds", "CloseTimeout", "closeTimeout");
|
|
1197
1393
|
if (closeMs != null) {
|
|
1198
|
-
|
|
1394
|
+
const normalized = normalizeWebSocketEndpointNumberInput(closeMs);
|
|
1395
|
+
target.CloseTimeoutSeconds = typeof normalized === "number"
|
|
1396
|
+
? normalized / 1000
|
|
1397
|
+
: normalized;
|
|
1199
1398
|
}
|
|
1200
1399
|
else if (closeSeconds != null) {
|
|
1201
|
-
target.CloseTimeoutSeconds = closeSeconds;
|
|
1400
|
+
target.CloseTimeoutSeconds = normalizeWebSocketEndpointNumberInput(closeSeconds);
|
|
1202
1401
|
}
|
|
1203
1402
|
const produce = pickEndpointFunction(raw, "Produce", "produce");
|
|
1204
1403
|
if (produce) {
|
|
@@ -1217,15 +1416,45 @@ function initializeWebSocketEndpointDefinitionModel(target, initial) {
|
|
|
1217
1416
|
target.ConsumeAsync = consumeAsync;
|
|
1218
1417
|
}
|
|
1219
1418
|
if (hasAnyEndpointField(raw, ["ConnectionMetadata", "connectionMetadata"])) {
|
|
1220
|
-
target.ConnectionMetadata =
|
|
1419
|
+
target.ConnectionMetadata = normalizeWebSocketEndpointMetadataInput(pickEndpointValue(raw, "ConnectionMetadata", "connectionMetadata"));
|
|
1221
1420
|
}
|
|
1222
1421
|
if (hasAnyEndpointField(raw, ["NativeClient", "nativeClient"])) {
|
|
1223
1422
|
const nativeClient = pickEndpointValue(raw, "NativeClient", "nativeClient");
|
|
1224
1423
|
target.NativeClient = nativeClient instanceof WebSocketNativeClientOptions
|
|
1225
1424
|
? nativeClient
|
|
1226
|
-
:
|
|
1425
|
+
: isRecord(nativeClient)
|
|
1426
|
+
? new WebSocketNativeClientOptions(nativeClient)
|
|
1427
|
+
: nativeClient;
|
|
1227
1428
|
}
|
|
1228
1429
|
}
|
|
1430
|
+
function normalizeWebSocketEndpointNumberInput(value) {
|
|
1431
|
+
if (typeof value === "number") {
|
|
1432
|
+
return value;
|
|
1433
|
+
}
|
|
1434
|
+
if (typeof value === "string" && value.trim()) {
|
|
1435
|
+
const parsed = Number(value);
|
|
1436
|
+
if (Number.isFinite(parsed)) {
|
|
1437
|
+
return parsed;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return value;
|
|
1441
|
+
}
|
|
1442
|
+
function normalizeWebSocketEndpointStringArrayInput(value) {
|
|
1443
|
+
if (!Array.isArray(value)) {
|
|
1444
|
+
return value;
|
|
1445
|
+
}
|
|
1446
|
+
return value.map((entry) => typeof entry === "string" ? entry.trim() : entry);
|
|
1447
|
+
}
|
|
1448
|
+
function normalizeWebSocketEndpointMetadataInput(value) {
|
|
1449
|
+
if (!isRecord(value)) {
|
|
1450
|
+
return value;
|
|
1451
|
+
}
|
|
1452
|
+
const normalized = {};
|
|
1453
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1454
|
+
normalized[key] = entry;
|
|
1455
|
+
}
|
|
1456
|
+
return normalized;
|
|
1457
|
+
}
|
|
1229
1458
|
function validateTrafficEndpointDefinitionModel(target) {
|
|
1230
1459
|
requireNonEmptyString(target.Name, "Endpoint name must be provided.");
|
|
1231
1460
|
if (target.Mode !== "Produce" && target.Mode !== "Consume") {
|
|
@@ -1559,13 +1788,12 @@ class CallbackAdapter {
|
|
|
1559
1788
|
return null;
|
|
1560
1789
|
}
|
|
1561
1790
|
const resolved = prepareProducedPayload(this.endpoint, payload);
|
|
1562
|
-
const
|
|
1563
|
-
if (
|
|
1564
|
-
return await invokeStructuredProduceDelegate(this.endpoint, resolved,
|
|
1791
|
+
const callbacks = selectProduceCallbackGroup(this.endpoint);
|
|
1792
|
+
if (callbacks.asyncDelegate) {
|
|
1793
|
+
return await invokeStructuredProduceDelegate(this.endpoint, resolved, callbacks.asyncDelegate);
|
|
1565
1794
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
const next = await delegate(clonePayload(resolved));
|
|
1795
|
+
if (callbacks.syncDelegate) {
|
|
1796
|
+
const next = await callbacks.syncDelegate(clonePayload(resolved));
|
|
1569
1797
|
return next == null ? null : normalizeTrackingPayload(next);
|
|
1570
1798
|
}
|
|
1571
1799
|
return clonePayload(resolved);
|
|
@@ -1577,19 +1805,17 @@ class CallbackAdapter {
|
|
|
1577
1805
|
if (this.consumeQueue.length > 0) {
|
|
1578
1806
|
return this.consumeQueue.shift() ?? null;
|
|
1579
1807
|
}
|
|
1580
|
-
const
|
|
1581
|
-
if (
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
this.ensureConsumeStreamStarted(streamDelegate);
|
|
1808
|
+
const callbacks = selectConsumeCallbackGroup(this.endpoint);
|
|
1809
|
+
if (callbacks.asyncDelegate) {
|
|
1810
|
+
if (callbacks.asyncDelegate.length === 0) {
|
|
1811
|
+
const next = await callbacks.asyncDelegate();
|
|
1812
|
+
return normalizeConsumedDelegatePayload(next);
|
|
1813
|
+
}
|
|
1814
|
+
this.ensureConsumeStreamStarted(callbacks.asyncDelegate);
|
|
1588
1815
|
return this.consumeQueue.shift() ?? null;
|
|
1589
1816
|
}
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
const next = await delegate();
|
|
1817
|
+
if (callbacks.syncDelegate) {
|
|
1818
|
+
const next = await callbacks.syncDelegate();
|
|
1593
1819
|
return next == null ? null : normalizeTrackingPayload(next);
|
|
1594
1820
|
}
|
|
1595
1821
|
return this.defaultPayload();
|
|
@@ -2355,7 +2581,610 @@ class DelegateStreamEndpointAdapter extends CallbackAdapter {
|
|
|
2355
2581
|
}
|
|
2356
2582
|
class GrpcEndpointAdapter extends CallbackAdapter {
|
|
2357
2583
|
}
|
|
2584
|
+
const NATIVE_WEBSOCKET_FRAME_QUEUE_CAPACITY = 64;
|
|
2585
|
+
const NATIVE_WEBSOCKET_FRAME_QUEUE_MAX_BYTES = 1024 * 1024;
|
|
2586
|
+
const NATIVE_WEBSOCKET_MAX_PAYLOAD_BYTES = 1024 * 1024;
|
|
2587
|
+
class NativeWebSocketAbortError extends Error {
|
|
2588
|
+
constructor() {
|
|
2589
|
+
super("WebSocket native client was interrupted.");
|
|
2590
|
+
this.name = "AbortError";
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
class NativeWebSocketPeerClosedError extends Error {
|
|
2594
|
+
constructor(code, reason) {
|
|
2595
|
+
super(code == null
|
|
2596
|
+
? "WebSocket peer closed before the operation completed."
|
|
2597
|
+
: `WebSocket peer closed before the operation completed (code ${code}${reason ? `: ${reason}` : ""}).`);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
class NativeWebSocketReceiveTimeoutError extends Error {
|
|
2601
|
+
constructor() {
|
|
2602
|
+
super("WebSocket receive timed out before the expected message arrived.");
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
class NativeWebSocketSendTimeoutError extends Error {
|
|
2606
|
+
constructor() {
|
|
2607
|
+
super("WebSocket send timed out before the frame was written.");
|
|
2608
|
+
this.code = "ETIMEDOUT";
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
class NativeWebSocketQueueOverflowError extends Error {
|
|
2612
|
+
constructor() {
|
|
2613
|
+
super("WebSocket receive queue exceeded its bounded frame limit.");
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
class NativeWebSocketFrameQueue {
|
|
2617
|
+
constructor(socket) {
|
|
2618
|
+
this.socket = socket;
|
|
2619
|
+
this.frames = [];
|
|
2620
|
+
this.queuedBytes = 0;
|
|
2621
|
+
this.terminalError = null;
|
|
2622
|
+
this.wakeResolver = null;
|
|
2623
|
+
this.onMessage = (data, isBinary) => {
|
|
2624
|
+
const copy = copyNativeWebSocketRawData(data);
|
|
2625
|
+
if (this.frames.length >= NATIVE_WEBSOCKET_FRAME_QUEUE_CAPACITY
|
|
2626
|
+
|| this.queuedBytes + copy.byteLength > NATIVE_WEBSOCKET_FRAME_QUEUE_MAX_BYTES) {
|
|
2627
|
+
this.fail(new NativeWebSocketQueueOverflowError());
|
|
2628
|
+
this.socket.terminate();
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
this.frames.push({ data: copy, isBinary });
|
|
2632
|
+
this.queuedBytes += copy.byteLength;
|
|
2633
|
+
this.wake();
|
|
2634
|
+
};
|
|
2635
|
+
this.onError = (error) => {
|
|
2636
|
+
this.fail(error);
|
|
2637
|
+
};
|
|
2638
|
+
this.onClose = (code, reason) => {
|
|
2639
|
+
this.fail(new NativeWebSocketPeerClosedError(code, reason.toString("utf8")));
|
|
2640
|
+
};
|
|
2641
|
+
socket.on("message", this.onMessage);
|
|
2642
|
+
socket.on("error", this.onError);
|
|
2643
|
+
socket.on("close", this.onClose);
|
|
2644
|
+
}
|
|
2645
|
+
async next(deadlineMs, signal) {
|
|
2646
|
+
while (true) {
|
|
2647
|
+
if (signal.aborted) {
|
|
2648
|
+
throw new NativeWebSocketAbortError();
|
|
2649
|
+
}
|
|
2650
|
+
if (isNativeWebSocketOverflowError(this.terminalError)) {
|
|
2651
|
+
throw this.terminalError;
|
|
2652
|
+
}
|
|
2653
|
+
const frame = this.frames.shift();
|
|
2654
|
+
if (frame) {
|
|
2655
|
+
this.queuedBytes -= frame.data.byteLength;
|
|
2656
|
+
return frame;
|
|
2657
|
+
}
|
|
2658
|
+
if (this.terminalError) {
|
|
2659
|
+
throw this.terminalError;
|
|
2660
|
+
}
|
|
2661
|
+
const remainingMs = deadlineMs - Date.now();
|
|
2662
|
+
if (remainingMs <= 0) {
|
|
2663
|
+
throw new NativeWebSocketReceiveTimeoutError();
|
|
2664
|
+
}
|
|
2665
|
+
await this.waitForFrame(remainingMs, signal);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
dispose() {
|
|
2669
|
+
this.socket.off("message", this.onMessage);
|
|
2670
|
+
this.socket.off("error", this.onError);
|
|
2671
|
+
this.socket.off("close", this.onClose);
|
|
2672
|
+
this.wake();
|
|
2673
|
+
}
|
|
2674
|
+
waitForFrame(timeoutMs, signal) {
|
|
2675
|
+
return new Promise((resolve, reject) => {
|
|
2676
|
+
let settled = false;
|
|
2677
|
+
const finish = (error) => {
|
|
2678
|
+
if (settled) {
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
settled = true;
|
|
2682
|
+
clearTimeout(timeout);
|
|
2683
|
+
signal.removeEventListener("abort", onAbort);
|
|
2684
|
+
if (this.wakeResolver === onWake) {
|
|
2685
|
+
this.wakeResolver = null;
|
|
2686
|
+
}
|
|
2687
|
+
if (error) {
|
|
2688
|
+
reject(error);
|
|
2689
|
+
}
|
|
2690
|
+
else {
|
|
2691
|
+
resolve();
|
|
2692
|
+
}
|
|
2693
|
+
};
|
|
2694
|
+
const onWake = () => finish();
|
|
2695
|
+
const onAbort = () => finish(new NativeWebSocketAbortError());
|
|
2696
|
+
const timeout = setTimeout(() => finish(new NativeWebSocketReceiveTimeoutError()), timeoutMs);
|
|
2697
|
+
this.wakeResolver = onWake;
|
|
2698
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2699
|
+
if (signal.aborted) {
|
|
2700
|
+
onAbort();
|
|
2701
|
+
}
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
fail(error) {
|
|
2705
|
+
this.terminalError ?? (this.terminalError = error);
|
|
2706
|
+
this.wake();
|
|
2707
|
+
}
|
|
2708
|
+
wake() {
|
|
2709
|
+
const resolve = this.wakeResolver;
|
|
2710
|
+
this.wakeResolver = null;
|
|
2711
|
+
resolve?.();
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
function isNativeWebSocketOverflowError(error) {
|
|
2715
|
+
return error instanceof NativeWebSocketQueueOverflowError
|
|
2716
|
+
|| String(error?.code ?? "") === "WS_ERR_UNSUPPORTED_MESSAGE_LENGTH";
|
|
2717
|
+
}
|
|
2718
|
+
function copyNativeWebSocketRawData(data) {
|
|
2719
|
+
if (Array.isArray(data)) {
|
|
2720
|
+
return new Uint8Array(Buffer.concat(data));
|
|
2721
|
+
}
|
|
2722
|
+
if (data instanceof ArrayBuffer) {
|
|
2723
|
+
return new Uint8Array(data.slice(0));
|
|
2724
|
+
}
|
|
2725
|
+
return new Uint8Array(Buffer.from(data));
|
|
2726
|
+
}
|
|
2358
2727
|
class WebSocketEndpointAdapter extends CallbackAdapter {
|
|
2728
|
+
constructor() {
|
|
2729
|
+
super(...arguments);
|
|
2730
|
+
this.nativeAbort = new AbortController();
|
|
2731
|
+
this.activeSockets = new Set();
|
|
2732
|
+
}
|
|
2733
|
+
async produce(payload) {
|
|
2734
|
+
if (this.endpoint.mode !== "Produce") {
|
|
2735
|
+
return null;
|
|
2736
|
+
}
|
|
2737
|
+
const delegates = selectProduceCallbackGroup(this.endpoint);
|
|
2738
|
+
if (delegates.asyncDelegate || delegates.syncDelegate) {
|
|
2739
|
+
return super.produce(payload);
|
|
2740
|
+
}
|
|
2741
|
+
const resolved = prepareProducedPayload(this.endpoint, payload);
|
|
2742
|
+
const configuration = normalizeNativeWebSocketConfiguration(this.endpoint, resolved);
|
|
2743
|
+
await this.executeNative(configuration);
|
|
2744
|
+
resolved.producedUtc ?? (resolved.producedUtc = new Date().toISOString());
|
|
2745
|
+
return resolved;
|
|
2746
|
+
}
|
|
2747
|
+
async consume() {
|
|
2748
|
+
if (this.endpoint.mode !== "Consume") {
|
|
2749
|
+
return null;
|
|
2750
|
+
}
|
|
2751
|
+
const delegates = selectConsumeCallbackGroup(this.endpoint);
|
|
2752
|
+
if (delegates.asyncDelegate || delegates.syncDelegate) {
|
|
2753
|
+
return super.consume();
|
|
2754
|
+
}
|
|
2755
|
+
const configuration = normalizeNativeWebSocketConfiguration(this.endpoint);
|
|
2756
|
+
return this.executeNative(configuration);
|
|
2757
|
+
}
|
|
2758
|
+
interrupt() {
|
|
2759
|
+
if (!this.nativeAbort.signal.aborted) {
|
|
2760
|
+
this.nativeAbort.abort();
|
|
2761
|
+
}
|
|
2762
|
+
for (const socket of this.activeSockets) {
|
|
2763
|
+
socket.terminate();
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
async dispose() {
|
|
2767
|
+
this.interrupt();
|
|
2768
|
+
}
|
|
2769
|
+
async executeNative(configuration) {
|
|
2770
|
+
const totalAttempts = configuration.maxAttempts + 1;
|
|
2771
|
+
let lastError;
|
|
2772
|
+
for (let attempt = 1; attempt <= totalAttempts; attempt += 1) {
|
|
2773
|
+
if (this.nativeAbort.signal.aborted) {
|
|
2774
|
+
throw new NativeWebSocketAbortError();
|
|
2775
|
+
}
|
|
2776
|
+
try {
|
|
2777
|
+
return await this.executeNativeOnce(configuration);
|
|
2778
|
+
}
|
|
2779
|
+
catch (error) {
|
|
2780
|
+
lastError = error;
|
|
2781
|
+
if (!isReconnectableNativeWebSocketError(error) || attempt >= totalAttempts) {
|
|
2782
|
+
throw error;
|
|
2783
|
+
}
|
|
2784
|
+
await waitForNativeWebSocketRetry(configuration.reconnectDelayMs, this.nativeAbort.signal);
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
throw lastError;
|
|
2788
|
+
}
|
|
2789
|
+
async executeNativeOnce(configuration) {
|
|
2790
|
+
const options = {
|
|
2791
|
+
headers: configuration.headers,
|
|
2792
|
+
handshakeTimeout: configuration.connectTimeoutMs,
|
|
2793
|
+
followRedirects: false,
|
|
2794
|
+
maxPayload: NATIVE_WEBSOCKET_MAX_PAYLOAD_BYTES
|
|
2795
|
+
};
|
|
2796
|
+
const socket = configuration.subprotocols.length > 0
|
|
2797
|
+
? new ws_1.default(configuration.url, configuration.subprotocols, options)
|
|
2798
|
+
: new ws_1.default(configuration.url, options);
|
|
2799
|
+
this.activeSockets.add(socket);
|
|
2800
|
+
const frames = new NativeWebSocketFrameQueue(socket);
|
|
2801
|
+
let pingTimer;
|
|
2802
|
+
try {
|
|
2803
|
+
await waitForNativeWebSocketOpen(socket, configuration.connectTimeoutMs, this.nativeAbort.signal);
|
|
2804
|
+
if (configuration.pingIntervalMs != null) {
|
|
2805
|
+
pingTimer = setInterval(() => {
|
|
2806
|
+
if (socket.readyState === ws_1.default.OPEN) {
|
|
2807
|
+
socket.ping();
|
|
2808
|
+
}
|
|
2809
|
+
}, configuration.pingIntervalMs);
|
|
2810
|
+
pingTimer.unref?.();
|
|
2811
|
+
}
|
|
2812
|
+
for (const message of configuration.messages) {
|
|
2813
|
+
await sendNativeWebSocketFrame(socket, message, configuration.connectTimeoutMs, this.nativeAbort.signal);
|
|
2814
|
+
}
|
|
2815
|
+
if (this.endpoint.mode === "Produce") {
|
|
2816
|
+
for (const expected of configuration.expectedMessages) {
|
|
2817
|
+
await receiveExpectedNativeWebSocketFrame(frames, expected, this.nativeAbort.signal);
|
|
2818
|
+
}
|
|
2819
|
+
return null;
|
|
2820
|
+
}
|
|
2821
|
+
if (configuration.expectedMessages.length > 0) {
|
|
2822
|
+
let payload = null;
|
|
2823
|
+
for (const expected of configuration.expectedMessages) {
|
|
2824
|
+
const frame = await receiveExpectedNativeWebSocketFrame(frames, expected, this.nativeAbort.signal);
|
|
2825
|
+
payload = nativeWebSocketFrameToTrackingPayload(frame);
|
|
2826
|
+
}
|
|
2827
|
+
return payload;
|
|
2828
|
+
}
|
|
2829
|
+
const frame = await frames.next(Date.now() + configuration.connectTimeoutMs, this.nativeAbort.signal);
|
|
2830
|
+
return nativeWebSocketFrameToTrackingPayload(frame);
|
|
2831
|
+
}
|
|
2832
|
+
finally {
|
|
2833
|
+
if (pingTimer) {
|
|
2834
|
+
clearInterval(pingTimer);
|
|
2835
|
+
}
|
|
2836
|
+
frames.dispose();
|
|
2837
|
+
await closeNativeWebSocket(socket, configuration.closeTimeoutMs);
|
|
2838
|
+
this.activeSockets.delete(socket);
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
function normalizeNativeWebSocketConfiguration(endpoint, payload) {
|
|
2843
|
+
const options = asRecordOrEmpty(endpoint.webSocket);
|
|
2844
|
+
const native = asRecordOrEmpty(pickEndpointValue(options, "NativeClient", "nativeClient"));
|
|
2845
|
+
const reconnect = asRecordOrEmpty(pickEndpointValue(native, "Reconnect", "reconnect"));
|
|
2846
|
+
if (payload) {
|
|
2847
|
+
validateNativeWebSocketHeaderLayer(payload.headers ?? {}, "payload headers");
|
|
2848
|
+
}
|
|
2849
|
+
const headers = mergeNativeWebSocketHeaders(toStringRecord(pickEndpointValue(native, "Headers", "headers")), resolveConnectionMetadata(endpoint), payload?.headers ?? {});
|
|
2850
|
+
const configuredCookies = pickEndpointValue(native, "Cookies", "cookies");
|
|
2851
|
+
const cookies = Array.isArray(configuredCookies)
|
|
2852
|
+
? configuredCookies.map((value) => String(value).trim())
|
|
2853
|
+
: [];
|
|
2854
|
+
if (cookies.length > 0) {
|
|
2855
|
+
const cookieName = Object.keys(headers).find((name) => name.toLowerCase() === "cookie");
|
|
2856
|
+
const existing = cookieName ? headers[cookieName] : "";
|
|
2857
|
+
if (cookieName) {
|
|
2858
|
+
delete headers[cookieName];
|
|
2859
|
+
}
|
|
2860
|
+
headers.Cookie = [existing, ...cookies].filter(Boolean).join("; ");
|
|
2861
|
+
}
|
|
2862
|
+
const rawMessages = pickEndpointValue(native, "Messages", "messages");
|
|
2863
|
+
const messages = Array.isArray(rawMessages)
|
|
2864
|
+
? rawMessages.map(normalizeNativeWebSocketMessage)
|
|
2865
|
+
: [];
|
|
2866
|
+
if (messages.length === 0 && payload) {
|
|
2867
|
+
const projected = projectNativeWebSocketPayload(payload.body);
|
|
2868
|
+
if (projected) {
|
|
2869
|
+
messages.push(projected);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
if (endpoint.mode === "Produce" && messages.length === 0) {
|
|
2873
|
+
throw new Error("Native WebSocket Produce requires at least one configured message or a projected payload body.");
|
|
2874
|
+
}
|
|
2875
|
+
const rawExpected = pickEndpointValue(native, "ExpectedMessages", "expectedMessages");
|
|
2876
|
+
const expectedMessages = Array.isArray(rawExpected)
|
|
2877
|
+
? rawExpected.map(normalizeNativeWebSocketExpectedMessage)
|
|
2878
|
+
: [];
|
|
2879
|
+
const connectTimeoutMs = nativeWebSocketTimeoutMs(options, 30000, ["ConnectTimeoutMs", "connectTimeoutMs"], ["ConnectTimeoutSeconds", "connectTimeoutSeconds", "ConnectTimeout", "connectTimeout"]);
|
|
2880
|
+
const closeTimeoutMs = nativeWebSocketTimeoutMs(options, 5000, ["CloseTimeoutMs", "closeTimeoutMs"], ["CloseTimeoutSeconds", "closeTimeoutSeconds", "CloseTimeout", "closeTimeout"]);
|
|
2881
|
+
const pingMsValue = pickEndpointValue(native, "PingIntervalMs", "pingIntervalMs");
|
|
2882
|
+
const pingSecondsValue = pickEndpointValue(native, "PingIntervalSeconds", "pingIntervalSeconds", "PingInterval", "pingInterval");
|
|
2883
|
+
const pingIntervalMs = pingMsValue != null
|
|
2884
|
+
? Number(pingMsValue)
|
|
2885
|
+
: (pingSecondsValue != null ? Number(pingSecondsValue) * 1000 : undefined);
|
|
2886
|
+
const delayMsValue = pickEndpointValue(reconnect, "DelayMs", "delayMs");
|
|
2887
|
+
const delaySecondsValue = pickEndpointValue(reconnect, "DelaySeconds", "delaySeconds", "Delay", "delay");
|
|
2888
|
+
return {
|
|
2889
|
+
url: optionString(options, "Url", "url"),
|
|
2890
|
+
subprotocols: nativeWebSocketStringArray(pickEndpointValue(options, "Subprotocols", "subprotocols")),
|
|
2891
|
+
connectTimeoutMs,
|
|
2892
|
+
closeTimeoutMs,
|
|
2893
|
+
headers,
|
|
2894
|
+
pingIntervalMs,
|
|
2895
|
+
maxAttempts: Number(pickEndpointValue(reconnect, "MaxAttempts", "maxAttempts") ?? 0),
|
|
2896
|
+
reconnectDelayMs: delayMsValue != null
|
|
2897
|
+
? Number(delayMsValue)
|
|
2898
|
+
: Number(delaySecondsValue ?? 1) * 1000,
|
|
2899
|
+
messages,
|
|
2900
|
+
expectedMessages
|
|
2901
|
+
};
|
|
2902
|
+
}
|
|
2903
|
+
function nativeWebSocketTimeoutMs(options, fallbackMs, millisecondKeys, secondKeys) {
|
|
2904
|
+
const millisecondValue = pickEndpointValue(options, ...millisecondKeys);
|
|
2905
|
+
if (millisecondValue != null) {
|
|
2906
|
+
return Number(millisecondValue);
|
|
2907
|
+
}
|
|
2908
|
+
const secondValue = pickEndpointValue(options, ...secondKeys);
|
|
2909
|
+
return secondValue == null ? fallbackMs : Number(secondValue) * 1000;
|
|
2910
|
+
}
|
|
2911
|
+
function nativeWebSocketStringArray(value) {
|
|
2912
|
+
return Array.isArray(value) ? value.map((entry) => String(entry).trim()) : [];
|
|
2913
|
+
}
|
|
2914
|
+
function normalizeNativeWebSocketMessage(value) {
|
|
2915
|
+
const options = asRecordOrEmpty(value);
|
|
2916
|
+
const kind = optionString(options, "Kind", "kind").toLowerCase();
|
|
2917
|
+
if (kind === "binary") {
|
|
2918
|
+
const bytes = pickEndpointValue(options, "BinaryPayload", "binaryPayload");
|
|
2919
|
+
return {
|
|
2920
|
+
data: bytes instanceof Uint8Array ? new Uint8Array(bytes) : Uint8Array.from(bytes),
|
|
2921
|
+
isBinary: true
|
|
2922
|
+
};
|
|
2923
|
+
}
|
|
2924
|
+
return {
|
|
2925
|
+
data: String(pickEndpointValue(options, "TextPayload", "textPayload") ?? ""),
|
|
2926
|
+
isBinary: false
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
function normalizeNativeWebSocketExpectedMessage(value) {
|
|
2930
|
+
const options = asRecordOrEmpty(value);
|
|
2931
|
+
const bytes = pickEndpointValue(options, "ContainsBytes", "containsBytes");
|
|
2932
|
+
return {
|
|
2933
|
+
containsText: pickEndpointValue(options, "ContainsText", "containsText") == null
|
|
2934
|
+
? undefined
|
|
2935
|
+
: String(pickEndpointValue(options, "ContainsText", "containsText")),
|
|
2936
|
+
containsBytes: bytes == null
|
|
2937
|
+
? undefined
|
|
2938
|
+
: (bytes instanceof Uint8Array ? new Uint8Array(bytes) : Uint8Array.from(bytes)),
|
|
2939
|
+
timeoutMs: nativeWebSocketTimeoutMs(options, 30000, ["TimeoutMs", "timeoutMs"], ["TimeoutSeconds", "timeoutSeconds", "Timeout", "timeout"])
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
function projectNativeWebSocketPayload(value) {
|
|
2943
|
+
if (value == null) {
|
|
2944
|
+
return null;
|
|
2945
|
+
}
|
|
2946
|
+
if (value instanceof Uint8Array) {
|
|
2947
|
+
return { data: new Uint8Array(value), isBinary: true };
|
|
2948
|
+
}
|
|
2949
|
+
if (value instanceof ArrayBuffer) {
|
|
2950
|
+
return { data: new Uint8Array(value), isBinary: true };
|
|
2951
|
+
}
|
|
2952
|
+
if (typeof value === "string") {
|
|
2953
|
+
return { data: value, isBinary: false };
|
|
2954
|
+
}
|
|
2955
|
+
const serialized = JSON.stringify(value);
|
|
2956
|
+
if (serialized === undefined) {
|
|
2957
|
+
throw new TypeError("Native WebSocket payload cannot be serialized as JSON.");
|
|
2958
|
+
}
|
|
2959
|
+
return {
|
|
2960
|
+
data: serialized,
|
|
2961
|
+
isBinary: false
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
function mergeNativeWebSocketHeaders(...layers) {
|
|
2965
|
+
const merged = new Map();
|
|
2966
|
+
for (const layer of layers) {
|
|
2967
|
+
for (const [name, value] of Object.entries(layer)) {
|
|
2968
|
+
merged.set(name.toLowerCase(), { name, value: String(value) });
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
return Object.fromEntries([...merged.values()].map(({ name, value }) => [name, value]));
|
|
2972
|
+
}
|
|
2973
|
+
function waitForNativeWebSocketOpen(socket, timeoutMs, signal) {
|
|
2974
|
+
return new Promise((resolve, reject) => {
|
|
2975
|
+
let settled = false;
|
|
2976
|
+
const finish = (error) => {
|
|
2977
|
+
if (settled) {
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
settled = true;
|
|
2981
|
+
clearTimeout(timeout);
|
|
2982
|
+
socket.off("open", onOpen);
|
|
2983
|
+
socket.off("error", onError);
|
|
2984
|
+
socket.off("close", onClose);
|
|
2985
|
+
socket.off("unexpected-response", onUnexpectedResponse);
|
|
2986
|
+
signal.removeEventListener("abort", onAbort);
|
|
2987
|
+
if (error) {
|
|
2988
|
+
reject(error);
|
|
2989
|
+
}
|
|
2990
|
+
else {
|
|
2991
|
+
resolve();
|
|
2992
|
+
}
|
|
2993
|
+
};
|
|
2994
|
+
const onOpen = () => finish();
|
|
2995
|
+
const onError = (error) => finish(error);
|
|
2996
|
+
const onClose = (code, reason) => {
|
|
2997
|
+
finish(new NativeWebSocketPeerClosedError(code, reason.toString("utf8")));
|
|
2998
|
+
};
|
|
2999
|
+
const onUnexpectedResponse = (_request, response) => {
|
|
3000
|
+
response.resume();
|
|
3001
|
+
finish(new Error(`WebSocket opening handshake was rejected with HTTP status ${response.statusCode ?? "unknown"}.`));
|
|
3002
|
+
socket.terminate();
|
|
3003
|
+
};
|
|
3004
|
+
const onAbort = () => {
|
|
3005
|
+
finish(new NativeWebSocketAbortError());
|
|
3006
|
+
socket.terminate();
|
|
3007
|
+
};
|
|
3008
|
+
const timeout = setTimeout(() => {
|
|
3009
|
+
const error = new Error("WebSocket connection timed out.");
|
|
3010
|
+
error.code = "ETIMEDOUT";
|
|
3011
|
+
finish(error);
|
|
3012
|
+
socket.terminate();
|
|
3013
|
+
}, timeoutMs);
|
|
3014
|
+
socket.once("open", onOpen);
|
|
3015
|
+
socket.once("error", onError);
|
|
3016
|
+
socket.once("close", onClose);
|
|
3017
|
+
socket.once("unexpected-response", onUnexpectedResponse);
|
|
3018
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3019
|
+
if (signal.aborted) {
|
|
3020
|
+
onAbort();
|
|
3021
|
+
}
|
|
3022
|
+
});
|
|
3023
|
+
}
|
|
3024
|
+
function sendNativeWebSocketFrame(socket, message, timeoutMs, signal) {
|
|
3025
|
+
if (signal.aborted) {
|
|
3026
|
+
return Promise.reject(new NativeWebSocketAbortError());
|
|
3027
|
+
}
|
|
3028
|
+
return new Promise((resolve, reject) => {
|
|
3029
|
+
let settled = false;
|
|
3030
|
+
let timeout;
|
|
3031
|
+
const finish = (error) => {
|
|
3032
|
+
if (settled) {
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3035
|
+
settled = true;
|
|
3036
|
+
if (timeout) {
|
|
3037
|
+
clearTimeout(timeout);
|
|
3038
|
+
}
|
|
3039
|
+
signal.removeEventListener("abort", onAbort);
|
|
3040
|
+
error ? reject(error) : resolve();
|
|
3041
|
+
};
|
|
3042
|
+
const onAbort = () => {
|
|
3043
|
+
finish(new NativeWebSocketAbortError());
|
|
3044
|
+
socket.terminate();
|
|
3045
|
+
};
|
|
3046
|
+
const onTimeout = () => {
|
|
3047
|
+
finish(new NativeWebSocketSendTimeoutError());
|
|
3048
|
+
socket.terminate();
|
|
3049
|
+
};
|
|
3050
|
+
timeout = setTimeout(onTimeout, timeoutMs);
|
|
3051
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3052
|
+
try {
|
|
3053
|
+
socket.send(message.data, { binary: message.isBinary }, (error) => {
|
|
3054
|
+
finish(error ? normalizeNativeWebSocketSendError(socket, error) : undefined);
|
|
3055
|
+
});
|
|
3056
|
+
}
|
|
3057
|
+
catch (error) {
|
|
3058
|
+
finish(normalizeNativeWebSocketSendError(socket, error));
|
|
3059
|
+
}
|
|
3060
|
+
});
|
|
3061
|
+
}
|
|
3062
|
+
function normalizeNativeWebSocketSendError(socket, error) {
|
|
3063
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
3064
|
+
const code = String(normalized.code ?? "");
|
|
3065
|
+
const isClosingState = socket.readyState === ws_1.default.CLOSING
|
|
3066
|
+
|| socket.readyState === ws_1.default.CLOSED;
|
|
3067
|
+
const isWsNotOpenStateError = /^WebSocket is not open: readyState [23] \((?:CLOSING|CLOSED)\)$/.test(normalized.message);
|
|
3068
|
+
return !code && isClosingState && isWsNotOpenStateError
|
|
3069
|
+
? new NativeWebSocketPeerClosedError()
|
|
3070
|
+
: normalized;
|
|
3071
|
+
}
|
|
3072
|
+
async function receiveExpectedNativeWebSocketFrame(frames, expected, signal) {
|
|
3073
|
+
const deadline = Date.now() + expected.timeoutMs;
|
|
3074
|
+
while (true) {
|
|
3075
|
+
const frame = await frames.next(deadline, signal);
|
|
3076
|
+
if (nativeWebSocketFrameMatches(frame, expected)) {
|
|
3077
|
+
return frame;
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
function nativeWebSocketFrameMatches(frame, expected) {
|
|
3082
|
+
if (expected.containsBytes) {
|
|
3083
|
+
return bufferContains(frame.data, expected.containsBytes);
|
|
3084
|
+
}
|
|
3085
|
+
if (expected.containsText != null) {
|
|
3086
|
+
return !frame.isBinary && Buffer.from(frame.data).toString("utf8").includes(expected.containsText);
|
|
3087
|
+
}
|
|
3088
|
+
return frame.data.byteLength > 0;
|
|
3089
|
+
}
|
|
3090
|
+
function bufferContains(value, expected) {
|
|
3091
|
+
if (expected.byteLength === 0) {
|
|
3092
|
+
return true;
|
|
3093
|
+
}
|
|
3094
|
+
return Buffer.from(value).indexOf(Buffer.from(expected)) >= 0;
|
|
3095
|
+
}
|
|
3096
|
+
function nativeWebSocketFrameToTrackingPayload(frame) {
|
|
3097
|
+
if (frame.isBinary) {
|
|
3098
|
+
return attachPayloadHelpers({
|
|
3099
|
+
headers: {},
|
|
3100
|
+
body: new Uint8Array(frame.data),
|
|
3101
|
+
contentType: "application/octet-stream"
|
|
3102
|
+
});
|
|
3103
|
+
}
|
|
3104
|
+
const text = Buffer.from(frame.data).toString("utf8");
|
|
3105
|
+
let body = text;
|
|
3106
|
+
let contentType = "text/plain";
|
|
3107
|
+
try {
|
|
3108
|
+
body = JSON.parse(text);
|
|
3109
|
+
contentType = "application/json";
|
|
3110
|
+
}
|
|
3111
|
+
catch {
|
|
3112
|
+
// Non-JSON text is returned verbatim.
|
|
3113
|
+
}
|
|
3114
|
+
return attachPayloadHelpers({ headers: {}, body, contentType });
|
|
3115
|
+
}
|
|
3116
|
+
function isReconnectableNativeWebSocketError(error) {
|
|
3117
|
+
if (error instanceof NativeWebSocketPeerClosedError) {
|
|
3118
|
+
return true;
|
|
3119
|
+
}
|
|
3120
|
+
if (!error || typeof error !== "object") {
|
|
3121
|
+
return false;
|
|
3122
|
+
}
|
|
3123
|
+
const code = String(error.code ?? "");
|
|
3124
|
+
return new Set([
|
|
3125
|
+
"ECONNABORTED",
|
|
3126
|
+
"ECONNREFUSED",
|
|
3127
|
+
"ECONNRESET",
|
|
3128
|
+
"EHOSTUNREACH",
|
|
3129
|
+
"ENETUNREACH",
|
|
3130
|
+
"ENOTFOUND",
|
|
3131
|
+
"EAI_AGAIN",
|
|
3132
|
+
"EPIPE",
|
|
3133
|
+
"ETIMEDOUT"
|
|
3134
|
+
]).has(code);
|
|
3135
|
+
}
|
|
3136
|
+
function waitForNativeWebSocketRetry(delayMs, signal) {
|
|
3137
|
+
if (signal.aborted) {
|
|
3138
|
+
return Promise.reject(new NativeWebSocketAbortError());
|
|
3139
|
+
}
|
|
3140
|
+
return new Promise((resolve, reject) => {
|
|
3141
|
+
const onAbort = () => {
|
|
3142
|
+
clearTimeout(timer);
|
|
3143
|
+
reject(new NativeWebSocketAbortError());
|
|
3144
|
+
};
|
|
3145
|
+
const timer = setTimeout(() => {
|
|
3146
|
+
signal.removeEventListener("abort", onAbort);
|
|
3147
|
+
resolve();
|
|
3148
|
+
}, delayMs);
|
|
3149
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3150
|
+
});
|
|
3151
|
+
}
|
|
3152
|
+
function closeNativeWebSocket(socket, timeoutMs) {
|
|
3153
|
+
if (socket.readyState === ws_1.default.CLOSED) {
|
|
3154
|
+
return Promise.resolve();
|
|
3155
|
+
}
|
|
3156
|
+
if (socket.readyState === ws_1.default.CONNECTING) {
|
|
3157
|
+
socket.terminate();
|
|
3158
|
+
return Promise.resolve();
|
|
3159
|
+
}
|
|
3160
|
+
return new Promise((resolve) => {
|
|
3161
|
+
let settled = false;
|
|
3162
|
+
const finish = () => {
|
|
3163
|
+
if (settled) {
|
|
3164
|
+
return;
|
|
3165
|
+
}
|
|
3166
|
+
settled = true;
|
|
3167
|
+
clearTimeout(timeout);
|
|
3168
|
+
socket.off("close", onClose);
|
|
3169
|
+
socket.off("error", onError);
|
|
3170
|
+
resolve();
|
|
3171
|
+
};
|
|
3172
|
+
const onClose = () => finish();
|
|
3173
|
+
const onError = () => finish();
|
|
3174
|
+
const timeout = setTimeout(() => {
|
|
3175
|
+
socket.terminate();
|
|
3176
|
+
finish();
|
|
3177
|
+
}, timeoutMs);
|
|
3178
|
+
socket.once("close", onClose);
|
|
3179
|
+
socket.once("error", onError);
|
|
3180
|
+
try {
|
|
3181
|
+
socket.close(1000);
|
|
3182
|
+
}
|
|
3183
|
+
catch {
|
|
3184
|
+
socket.terminate();
|
|
3185
|
+
finish();
|
|
3186
|
+
}
|
|
3187
|
+
});
|
|
2359
3188
|
}
|
|
2360
3189
|
class EndpointAdapterFactory {
|
|
2361
3190
|
static create(endpoint) {
|
|
@@ -2364,6 +3193,7 @@ class EndpointAdapterFactory {
|
|
|
2364
3193
|
}
|
|
2365
3194
|
const normalized = normalizeEndpointDefinition(endpoint);
|
|
2366
3195
|
validateEndpointDefinition(normalized);
|
|
3196
|
+
validateRawNativeWebSocketHeaderInputs(endpoint, normalized);
|
|
2367
3197
|
switch (normalized.kind) {
|
|
2368
3198
|
case "Http":
|
|
2369
3199
|
return new HttpEndpointAdapter(normalized);
|
|
@@ -2393,6 +3223,56 @@ class EndpointAdapterFactory {
|
|
|
2393
3223
|
}
|
|
2394
3224
|
}
|
|
2395
3225
|
exports.EndpointAdapterFactory = EndpointAdapterFactory;
|
|
3226
|
+
function validateRawNativeWebSocketHeaderInputs(input, endpoint) {
|
|
3227
|
+
if (endpoint.kind !== "WebSocket") {
|
|
3228
|
+
return;
|
|
3229
|
+
}
|
|
3230
|
+
const callbacks = endpoint.mode === "Produce"
|
|
3231
|
+
? selectProduceCallbackGroup(endpoint)
|
|
3232
|
+
: selectConsumeCallbackGroup(endpoint);
|
|
3233
|
+
if (callbacks.asyncDelegate || callbacks.syncDelegate) {
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
const raw = asRecordOrEmpty(input);
|
|
3237
|
+
const options = pickEndpointTransportRecord(raw, "WebSocket", "WebSocket", ["webSocket", "WebSocket"], WEB_SOCKET_ENDPOINT_FLAT_KEYS);
|
|
3238
|
+
if (pickEndpointValue(options, "NativeClient", "nativeClient") == null) {
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
const protocolMetadata = pickEndpointValue(options, "ConnectionMetadata", "connectionMetadata");
|
|
3242
|
+
if (protocolMetadata != null) {
|
|
3243
|
+
validateNativeWebSocketHeaderLayer(protocolMetadata, "ConnectionMetadata");
|
|
3244
|
+
}
|
|
3245
|
+
const rootMetadata = pickEndpointValue(raw, "ConnectionMetadata", "connectionMetadata");
|
|
3246
|
+
if (rootMetadata != null && rootMetadata !== protocolMetadata) {
|
|
3247
|
+
validateNativeWebSocketHeaderLayer(rootMetadata, "ConnectionMetadata");
|
|
3248
|
+
}
|
|
3249
|
+
const delegate = pickEndpointValue(raw, "Delegate", "delegate", "DelegateStream");
|
|
3250
|
+
if (isRecord(delegate)) {
|
|
3251
|
+
const delegateMetadata = pickEndpointValue(delegate, "ConnectionMetadata", "connectionMetadata");
|
|
3252
|
+
if (delegateMetadata != null) {
|
|
3253
|
+
validateNativeWebSocketHeaderLayer(delegateMetadata, "ConnectionMetadata");
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
function validateNativeEndpointExecutionSupport(endpoint) {
|
|
3258
|
+
const normalized = normalizeEndpointDefinition(endpoint);
|
|
3259
|
+
if (!normalized || typeof normalized !== "object") {
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
validateRawNativeWebSocketHeaderInputs(endpoint, normalized);
|
|
3263
|
+
const kind = String(normalized.kind ?? "").trim().toLowerCase();
|
|
3264
|
+
const modeToken = String(normalized.mode ?? "").trim().toLowerCase();
|
|
3265
|
+
if (modeToken !== "produce" && modeToken !== "consume") {
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
const mode = modeToken === "produce" ? "Produce" : "Consume";
|
|
3269
|
+
if (kind === "grpc") {
|
|
3270
|
+
validateGrpcEndpoint(normalized, mode);
|
|
3271
|
+
}
|
|
3272
|
+
else if (kind === "websocket") {
|
|
3273
|
+
validateWebSocketEndpoint(normalized, mode);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
2396
3276
|
const HTTP_ENDPOINT_FLAT_KEYS = [
|
|
2397
3277
|
"Url",
|
|
2398
3278
|
"url",
|
|
@@ -2619,7 +3499,9 @@ const WEB_SOCKET_ENDPOINT_FLAT_KEYS = [
|
|
|
2619
3499
|
"ConsumeAsync",
|
|
2620
3500
|
"consumeAsync",
|
|
2621
3501
|
"ConnectionMetadata",
|
|
2622
|
-
"connectionMetadata"
|
|
3502
|
+
"connectionMetadata",
|
|
3503
|
+
"NativeClient",
|
|
3504
|
+
"nativeClient"
|
|
2623
3505
|
];
|
|
2624
3506
|
function normalizeEndpointDefinition(endpoint) {
|
|
2625
3507
|
if (!endpoint || typeof endpoint !== "object") {
|
|
@@ -2925,6 +3807,10 @@ function pickOptionalEndpointString(record, ...keys) {
|
|
|
2925
3807
|
const value = optionString(record, ...keys).trim();
|
|
2926
3808
|
return value ? value : undefined;
|
|
2927
3809
|
}
|
|
3810
|
+
function pickOptionalEndpointExactString(record, ...keys) {
|
|
3811
|
+
const value = pickEndpointValue(record, ...keys);
|
|
3812
|
+
return typeof value === "string" ? value : undefined;
|
|
3813
|
+
}
|
|
2928
3814
|
function pickOptionalEndpointStringAllowEmpty(record, ...keys) {
|
|
2929
3815
|
const value = pickEndpointValue(record, ...keys);
|
|
2930
3816
|
return typeof value === "string" ? value.trim() : undefined;
|
|
@@ -3028,8 +3914,10 @@ function validateEndpointDefinition(endpoint) {
|
|
|
3028
3914
|
if (mode !== "Produce" && mode !== "Consume") {
|
|
3029
3915
|
throw new Error(`Unsupported endpoint mode: ${mode}.`);
|
|
3030
3916
|
}
|
|
3031
|
-
const
|
|
3032
|
-
const
|
|
3917
|
+
const produceCallbacks = selectProduceCallbackGroup(endpoint);
|
|
3918
|
+
const consumeCallbacks = selectConsumeCallbackGroup(endpoint);
|
|
3919
|
+
const hasProduceDelegate = Boolean(produceCallbacks.asyncDelegate || produceCallbacks.syncDelegate);
|
|
3920
|
+
const hasConsumeDelegate = Boolean(consumeCallbacks.asyncDelegate || consumeCallbacks.syncDelegate);
|
|
3033
3921
|
const hasModeDelegate = mode === "Produce" ? hasProduceDelegate : hasConsumeDelegate;
|
|
3034
3922
|
switch (kind) {
|
|
3035
3923
|
case "Http":
|
|
@@ -3141,7 +4029,14 @@ function validateDelegateStreamEndpoint(endpoint, mode) {
|
|
|
3141
4029
|
}
|
|
3142
4030
|
}
|
|
3143
4031
|
function validateGrpcEndpoint(endpoint, mode) {
|
|
4032
|
+
const callbacks = mode === "Produce"
|
|
4033
|
+
? selectProduceCallbackGroup(endpoint)
|
|
4034
|
+
: selectConsumeCallbackGroup(endpoint);
|
|
3144
4035
|
const options = asRecordOrEmpty(endpoint.grpc);
|
|
4036
|
+
const nativeClient = pickEndpointValue(options, "NativeClient", "nativeClient");
|
|
4037
|
+
if (nativeClient != null && !callbacks.asyncDelegate && !callbacks.syncDelegate) {
|
|
4038
|
+
throw new Error(NATIVE_GRPC_EXECUTION_UNAVAILABLE_MESSAGE);
|
|
4039
|
+
}
|
|
3145
4040
|
requireNonEmptyString(optionString(options, "Target", "target"), "Target must be provided for gRPC endpoint definitions.");
|
|
3146
4041
|
requireNonEmptyString(optionString(options, "ServiceName", "serviceName"), "ServiceName must be provided for gRPC endpoint definitions.");
|
|
3147
4042
|
requireNonEmptyString(optionString(options, "MethodName", "methodName"), "MethodName must be provided for gRPC endpoint definitions.");
|
|
@@ -3152,18 +4047,17 @@ function validateGrpcEndpoint(endpoint, mode) {
|
|
|
3152
4047
|
|| (hasOptionValue(options, "DeadlineSeconds", "deadlineSeconds", "Deadline", "deadline") && deadlineSeconds <= 0)) {
|
|
3153
4048
|
throw new RangeError("Deadline must be greater than zero.");
|
|
3154
4049
|
}
|
|
3155
|
-
|
|
3156
|
-
if (nativeClient != null) {
|
|
3157
|
-
new GrpcNativeClientOptions(asRecordOrEmpty(nativeClient)).validate();
|
|
3158
|
-
}
|
|
3159
|
-
if (mode === "Produce" && !resolveStructuredProduceDelegate(endpoint) && !resolveProduceDelegate(endpoint) && nativeClient == null) {
|
|
4050
|
+
if (mode === "Produce" && !callbacks.asyncDelegate && !callbacks.syncDelegate && nativeClient == null) {
|
|
3160
4051
|
throw new Error("ProduceAsync delegate must be provided when endpoint mode is Produce.");
|
|
3161
4052
|
}
|
|
3162
|
-
if (mode === "Consume" && !
|
|
4053
|
+
if (mode === "Consume" && !callbacks.asyncDelegate && !callbacks.syncDelegate && nativeClient == null) {
|
|
3163
4054
|
throw new Error("ConsumeAsync delegate must be provided when endpoint mode is Consume.");
|
|
3164
4055
|
}
|
|
3165
4056
|
}
|
|
3166
4057
|
function validateWebSocketEndpoint(endpoint, mode) {
|
|
4058
|
+
const callbacks = mode === "Produce"
|
|
4059
|
+
? selectProduceCallbackGroup(endpoint)
|
|
4060
|
+
: selectConsumeCallbackGroup(endpoint);
|
|
3167
4061
|
const options = asRecordOrEmpty(endpoint.webSocket);
|
|
3168
4062
|
const url = requireNonEmptyString(optionString(options, "Url", "url"), "Url must be provided for WebSocket endpoint definitions.");
|
|
3169
4063
|
let parsed;
|
|
@@ -3173,32 +4067,277 @@ function validateWebSocketEndpoint(endpoint, mode) {
|
|
|
3173
4067
|
catch {
|
|
3174
4068
|
throw new Error("Url must be an absolute WebSocket URI using the ws or wss scheme.");
|
|
3175
4069
|
}
|
|
3176
|
-
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:")
|
|
4070
|
+
if ((parsed.protocol !== "ws:" && parsed.protocol !== "wss:")
|
|
4071
|
+
|| !parsed.hostname
|
|
4072
|
+
|| Boolean(parsed.hash)) {
|
|
3177
4073
|
throw new Error("Url must be an absolute WebSocket URI using the ws or wss scheme.");
|
|
3178
4074
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
throw new RangeError("CloseTimeout must be greater than zero.");
|
|
3190
|
-
}
|
|
4075
|
+
validateNativeWebSocketTimerDelay(hasOptionValue(options, "ConnectTimeoutMs", "connectTimeoutMs")
|
|
4076
|
+
? optionNumber(options, "ConnectTimeoutMs", "connectTimeoutMs")
|
|
4077
|
+
: undefined, hasOptionValue(options, "ConnectTimeoutSeconds", "connectTimeoutSeconds", "ConnectTimeout", "connectTimeout")
|
|
4078
|
+
? optionNumber(options, "ConnectTimeoutSeconds", "connectTimeoutSeconds", "ConnectTimeout", "connectTimeout")
|
|
4079
|
+
: undefined, "ConnectTimeout");
|
|
4080
|
+
validateNativeWebSocketTimerDelay(hasOptionValue(options, "CloseTimeoutMs", "closeTimeoutMs")
|
|
4081
|
+
? optionNumber(options, "CloseTimeoutMs", "closeTimeoutMs")
|
|
4082
|
+
: undefined, hasOptionValue(options, "CloseTimeoutSeconds", "closeTimeoutSeconds", "CloseTimeout", "closeTimeout")
|
|
4083
|
+
? optionNumber(options, "CloseTimeoutSeconds", "closeTimeoutSeconds", "CloseTimeout", "closeTimeout")
|
|
4084
|
+
: undefined, "CloseTimeout");
|
|
3191
4085
|
const nativeClient = pickEndpointValue(options, "NativeClient", "nativeClient");
|
|
3192
|
-
|
|
3193
|
-
|
|
4086
|
+
const hasSelectedDelegate = Boolean(callbacks.asyncDelegate || callbacks.syncDelegate);
|
|
4087
|
+
if (nativeClient != null && !hasSelectedDelegate) {
|
|
4088
|
+
if (mode === "Consume" && endpoint.trackingField.trim().toLowerCase().startsWith("header:")) {
|
|
4089
|
+
throw new Error("Native WebSocket Consume cannot use a header TrackingField because WebSocket messages do not contain HTTP response headers.");
|
|
4090
|
+
}
|
|
4091
|
+
if (mode === "Consume" && endpoint.gatherByField?.trim().toLowerCase().startsWith("header:")) {
|
|
4092
|
+
throw new Error("Native WebSocket Consume cannot use a header GatherByField because WebSocket messages do not contain HTTP response headers.");
|
|
4093
|
+
}
|
|
4094
|
+
validateNativeWebSocketSubprotocols(pickEndpointValue(options, "Subprotocols", "subprotocols"));
|
|
4095
|
+
validateNativeWebSocketOptionsWithInstance(nativeClient);
|
|
4096
|
+
validateNativeWebSocketHeaderLayer(resolveConnectionMetadata(endpoint), "ConnectionMetadata");
|
|
3194
4097
|
}
|
|
3195
|
-
if (mode === "Produce" && !
|
|
4098
|
+
if (mode === "Produce" && !callbacks.asyncDelegate && !callbacks.syncDelegate && nativeClient == null) {
|
|
3196
4099
|
throw new Error("ProduceAsync delegate must be provided when endpoint mode is Produce.");
|
|
3197
4100
|
}
|
|
3198
|
-
if (mode === "Consume" && !
|
|
4101
|
+
if (mode === "Consume" && !callbacks.asyncDelegate && !callbacks.syncDelegate && nativeClient == null) {
|
|
3199
4102
|
throw new Error("ConsumeAsync delegate must be provided when endpoint mode is Consume.");
|
|
3200
4103
|
}
|
|
3201
4104
|
}
|
|
4105
|
+
const NATIVE_WEBSOCKET_MANAGED_HEADERS = new Set([
|
|
4106
|
+
"connection",
|
|
4107
|
+
"content-length",
|
|
4108
|
+
"expect",
|
|
4109
|
+
"host",
|
|
4110
|
+
"sec-websocket-accept",
|
|
4111
|
+
"sec-websocket-extensions",
|
|
4112
|
+
"sec-websocket-key",
|
|
4113
|
+
"sec-websocket-protocol",
|
|
4114
|
+
"sec-websocket-version",
|
|
4115
|
+
"upgrade"
|
|
4116
|
+
]);
|
|
4117
|
+
const NATIVE_WEBSOCKET_HTTP_TOKEN_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
4118
|
+
const NATIVE_WEBSOCKET_MAX_RECONNECT_ATTEMPTS = 2147483646;
|
|
4119
|
+
const NATIVE_WEBSOCKET_MAX_TIMER_DELAY_MS = 2147483647;
|
|
4120
|
+
function validateNativeWebSocketReconnectPolicy(value) {
|
|
4121
|
+
if (!isRecord(value)) {
|
|
4122
|
+
throw new TypeError("WebSocket NativeClient.Reconnect must be an object.");
|
|
4123
|
+
}
|
|
4124
|
+
const maxAttempts = pickEndpointValue(value, "MaxAttempts", "maxAttempts");
|
|
4125
|
+
if (maxAttempts != null) {
|
|
4126
|
+
const number = nativeWebSocketStrictNumber(maxAttempts, "MaxAttempts");
|
|
4127
|
+
if (!Number.isInteger(number) || number < 0 || number > NATIVE_WEBSOCKET_MAX_RECONNECT_ATTEMPTS) {
|
|
4128
|
+
throw new RangeError("MaxAttempts must be a finite nonnegative whole number.");
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
validateNativeWebSocketTimerDelay(pickEndpointValue(value, "DelayMs", "delayMs"), pickEndpointValue(value, "DelaySeconds", "delaySeconds", "Delay", "delay"), "Delay", true);
|
|
4132
|
+
}
|
|
4133
|
+
function validateNativeWebSocketOptions(value) {
|
|
4134
|
+
if (!isRecord(value)) {
|
|
4135
|
+
throw new TypeError("WebSocket NativeClient must be an object.");
|
|
4136
|
+
}
|
|
4137
|
+
const native = value;
|
|
4138
|
+
const headers = pickEndpointValue(native, "Headers", "headers");
|
|
4139
|
+
if (headers != null) {
|
|
4140
|
+
validateNativeWebSocketHeaderLayer(headers, "NativeClient.Headers");
|
|
4141
|
+
}
|
|
4142
|
+
const cookies = pickEndpointValue(native, "Cookies", "cookies");
|
|
4143
|
+
if (cookies != null && !Array.isArray(cookies)) {
|
|
4144
|
+
throw new TypeError("WebSocket NativeClient.Cookies must be an array.");
|
|
4145
|
+
}
|
|
4146
|
+
if (Array.isArray(cookies)) {
|
|
4147
|
+
cookies.forEach((cookie, index) => {
|
|
4148
|
+
if (typeof cookie !== "string") {
|
|
4149
|
+
throw new TypeError(`WebSocket NativeClient.Cookies[${index}] must be a string.`);
|
|
4150
|
+
}
|
|
4151
|
+
if (!cookie.trim()) {
|
|
4152
|
+
throw new Error(`WebSocket NativeClient.Cookies[${index}] must not be blank.`);
|
|
4153
|
+
}
|
|
4154
|
+
if (/\r|\n/.test(cookie)) {
|
|
4155
|
+
throw new Error(`WebSocket NativeClient.Cookies[${index}] must not contain CR or LF characters.`);
|
|
4156
|
+
}
|
|
4157
|
+
});
|
|
4158
|
+
}
|
|
4159
|
+
const pingMs = pickEndpointValue(native, "PingIntervalMs", "pingIntervalMs");
|
|
4160
|
+
const pingSeconds = pickEndpointValue(native, "PingIntervalSeconds", "pingIntervalSeconds", "PingInterval", "pingInterval");
|
|
4161
|
+
validateNativeWebSocketTimerDelay(pingMs, pingSeconds, "PingInterval");
|
|
4162
|
+
const reconnectValue = pickEndpointValue(native, "Reconnect", "reconnect");
|
|
4163
|
+
if (reconnectValue != null) {
|
|
4164
|
+
validateNativeWebSocketReconnectPolicy(reconnectValue);
|
|
4165
|
+
if (reconnectValue instanceof WebSocketReconnectPolicy) {
|
|
4166
|
+
reconnectValue.validate();
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
const messages = pickEndpointValue(native, "Messages", "messages");
|
|
4170
|
+
if (messages != null && !Array.isArray(messages)) {
|
|
4171
|
+
throw new TypeError("WebSocket NativeClient.Messages must be an array.");
|
|
4172
|
+
}
|
|
4173
|
+
if (Array.isArray(messages)) {
|
|
4174
|
+
for (const message of messages) {
|
|
4175
|
+
if (message instanceof WebSocketMessageSpec) {
|
|
4176
|
+
message.validate();
|
|
4177
|
+
}
|
|
4178
|
+
else {
|
|
4179
|
+
validateNativeWebSocketMessage(message);
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
const expectedMessages = pickEndpointValue(native, "ExpectedMessages", "expectedMessages");
|
|
4184
|
+
if (expectedMessages != null && !Array.isArray(expectedMessages)) {
|
|
4185
|
+
throw new TypeError("WebSocket NativeClient.ExpectedMessages must be an array.");
|
|
4186
|
+
}
|
|
4187
|
+
if (Array.isArray(expectedMessages)) {
|
|
4188
|
+
for (const expected of expectedMessages) {
|
|
4189
|
+
if (expected instanceof WebSocketExpectedMessage) {
|
|
4190
|
+
expected.validate();
|
|
4191
|
+
}
|
|
4192
|
+
else {
|
|
4193
|
+
validateNativeWebSocketExpectedMessage(expected);
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
function validateNativeWebSocketOptionsWithInstance(value) {
|
|
4199
|
+
validateNativeWebSocketOptions(value);
|
|
4200
|
+
if (value instanceof WebSocketNativeClientOptions) {
|
|
4201
|
+
value.validate();
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
function validateNativeWebSocketHeaderLayer(value, layerName) {
|
|
4205
|
+
if (!isRecord(value)) {
|
|
4206
|
+
throw new TypeError(`WebSocket ${layerName} must be an object.`);
|
|
4207
|
+
}
|
|
4208
|
+
for (const [name, headerValue] of Object.entries(value)) {
|
|
4209
|
+
const normalizedName = name.trim();
|
|
4210
|
+
if (!normalizedName) {
|
|
4211
|
+
throw new Error(`WebSocket ${layerName} contains an empty header name.`);
|
|
4212
|
+
}
|
|
4213
|
+
if (name !== normalizedName) {
|
|
4214
|
+
throw new Error(`WebSocket ${layerName} header names must not contain surrounding whitespace.`);
|
|
4215
|
+
}
|
|
4216
|
+
if (/\r|\n/.test(name) || /\r|\n/.test(String(headerValue ?? ""))) {
|
|
4217
|
+
throw new Error(`WebSocket ${layerName} must not contain CR or LF characters.`);
|
|
4218
|
+
}
|
|
4219
|
+
if (!NATIVE_WEBSOCKET_HTTP_TOKEN_PATTERN.test(normalizedName)) {
|
|
4220
|
+
throw new Error(`WebSocket ${layerName} contains an invalid HTTP header name.`);
|
|
4221
|
+
}
|
|
4222
|
+
if (typeof headerValue !== "string") {
|
|
4223
|
+
throw new TypeError(`WebSocket ${layerName} header values must be strings.`);
|
|
4224
|
+
}
|
|
4225
|
+
if (NATIVE_WEBSOCKET_MANAGED_HEADERS.has(normalizedName.toLowerCase())) {
|
|
4226
|
+
throw new Error(`WebSocket ${layerName} must not configure ws-managed header '${normalizedName}'.`);
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
function validateNativeWebSocketSubprotocols(value) {
|
|
4231
|
+
if (value == null) {
|
|
4232
|
+
return;
|
|
4233
|
+
}
|
|
4234
|
+
if (!Array.isArray(value)) {
|
|
4235
|
+
throw new TypeError("WebSocket Subprotocols must be an array.");
|
|
4236
|
+
}
|
|
4237
|
+
const seen = new Set();
|
|
4238
|
+
value.forEach((protocol, index) => {
|
|
4239
|
+
if (typeof protocol !== "string" || !protocol.trim()) {
|
|
4240
|
+
throw new TypeError(`WebSocket Subprotocols[${index}] must be a nonblank string.`);
|
|
4241
|
+
}
|
|
4242
|
+
const normalized = protocol.trim();
|
|
4243
|
+
if (!NATIVE_WEBSOCKET_HTTP_TOKEN_PATTERN.test(normalized)) {
|
|
4244
|
+
throw new Error(`WebSocket Subprotocols[${index}] must be a valid RFC 6455 protocol token.`);
|
|
4245
|
+
}
|
|
4246
|
+
if (seen.has(normalized)) {
|
|
4247
|
+
throw new Error("WebSocket Subprotocols must not contain duplicate values.");
|
|
4248
|
+
}
|
|
4249
|
+
seen.add(normalized);
|
|
4250
|
+
});
|
|
4251
|
+
}
|
|
4252
|
+
function validateOriginalNativeWebSocketMessage(value) {
|
|
4253
|
+
validateNativeWebSocketMessage({
|
|
4254
|
+
Kind: pickEndpointValue(value, "Kind", "kind") ?? "Text",
|
|
4255
|
+
TextPayload: pickEndpointValue(value, "TextPayload", "textPayload"),
|
|
4256
|
+
BinaryPayload: pickEndpointValue(value, "BinaryPayload", "binaryPayload")
|
|
4257
|
+
});
|
|
4258
|
+
}
|
|
4259
|
+
function validateNativeWebSocketMessage(value) {
|
|
4260
|
+
if (!isRecord(value)) {
|
|
4261
|
+
throw new TypeError("WebSocket NativeClient.Messages entries must be message objects.");
|
|
4262
|
+
}
|
|
4263
|
+
const kind = optionString(value, "Kind", "kind").toLowerCase();
|
|
4264
|
+
if (kind !== "text" && kind !== "binary") {
|
|
4265
|
+
throw new Error("WebSocket Message kind must be Text or Binary.");
|
|
4266
|
+
}
|
|
4267
|
+
if (kind === "text") {
|
|
4268
|
+
if (typeof pickEndpointValue(value, "TextPayload", "textPayload") !== "string") {
|
|
4269
|
+
throw new TypeError("Text WebSocket messages require TextPayload.");
|
|
4270
|
+
}
|
|
4271
|
+
return;
|
|
4272
|
+
}
|
|
4273
|
+
validateNativeWebSocketBytes(pickEndpointValue(value, "BinaryPayload", "binaryPayload"), "WebSocket binary payload");
|
|
4274
|
+
}
|
|
4275
|
+
function validateNativeWebSocketExpectedMessage(value) {
|
|
4276
|
+
if (!isRecord(value)) {
|
|
4277
|
+
throw new TypeError("WebSocket NativeClient.ExpectedMessages entries must be expected-message objects.");
|
|
4278
|
+
}
|
|
4279
|
+
const containsText = pickEndpointValue(value, "ContainsText", "containsText");
|
|
4280
|
+
if (containsText != null && typeof containsText !== "string") {
|
|
4281
|
+
throw new TypeError("WebSocket expected ContainsText must be a string.");
|
|
4282
|
+
}
|
|
4283
|
+
const containsBytes = pickEndpointValue(value, "ContainsBytes", "containsBytes");
|
|
4284
|
+
if (containsBytes != null) {
|
|
4285
|
+
validateNativeWebSocketBytes(containsBytes, "WebSocket expected ContainsBytes");
|
|
4286
|
+
}
|
|
4287
|
+
const timeoutMs = pickEndpointValue(value, "TimeoutMs", "timeoutMs");
|
|
4288
|
+
const timeoutSeconds = pickEndpointValue(value, "TimeoutSeconds", "timeoutSeconds", "Timeout", "timeout");
|
|
4289
|
+
validateNativeWebSocketTimerDelay(timeoutMs, timeoutSeconds, "Timeout");
|
|
4290
|
+
}
|
|
4291
|
+
function validateNativeWebSocketBytes(value, fieldName) {
|
|
4292
|
+
if (value instanceof Uint8Array) {
|
|
4293
|
+
return;
|
|
4294
|
+
}
|
|
4295
|
+
if (!Array.isArray(value) || value.some((byte) => (typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255))) {
|
|
4296
|
+
throw new TypeError(`${fieldName} must contain only byte values from 0 through 255.`);
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
function validateNativeWebSocketTimerDelay(millisecondValue, secondValue, fieldName, allowZero = false) {
|
|
4300
|
+
const validateValue = (value, multiplier) => {
|
|
4301
|
+
const number = nativeWebSocketStrictNumber(value, fieldName);
|
|
4302
|
+
if (allowZero ? number < 0 : number <= 0) {
|
|
4303
|
+
throw new RangeError(allowZero
|
|
4304
|
+
? `${fieldName} must be zero or greater and finite.`
|
|
4305
|
+
: `${fieldName} must be greater than zero and finite.`);
|
|
4306
|
+
}
|
|
4307
|
+
if (number > NATIVE_WEBSOCKET_MAX_TIMER_DELAY_MS / multiplier) {
|
|
4308
|
+
throw new RangeError(`${fieldName} must not exceed 2,147,483,647 milliseconds.`);
|
|
4309
|
+
}
|
|
4310
|
+
return number * multiplier;
|
|
4311
|
+
};
|
|
4312
|
+
const milliseconds = millisecondValue == null
|
|
4313
|
+
? undefined
|
|
4314
|
+
: validateValue(millisecondValue, 1);
|
|
4315
|
+
const seconds = secondValue == null
|
|
4316
|
+
? undefined
|
|
4317
|
+
: validateValue(secondValue, 1000);
|
|
4318
|
+
return milliseconds ?? seconds;
|
|
4319
|
+
}
|
|
4320
|
+
function nativeWebSocketStrictNumber(value, fieldName) {
|
|
4321
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
4322
|
+
throw new TypeError(`${fieldName} must be a finite number.`);
|
|
4323
|
+
}
|
|
4324
|
+
return value;
|
|
4325
|
+
}
|
|
4326
|
+
function validateUnsupportedNativeEndpointMode(endpoint, protocol, mode, hasSelectedDelegate) {
|
|
4327
|
+
const options = protocol === "gRPC"
|
|
4328
|
+
? asRecordOrEmpty(endpoint.grpc)
|
|
4329
|
+
: asRecordOrEmpty(endpoint.webSocket);
|
|
4330
|
+
const nativeClient = pickEndpointValue(options, "NativeClient", "nativeClient");
|
|
4331
|
+
if (nativeClient != null && !hasSelectedDelegate) {
|
|
4332
|
+
throw unsupportedNativeEndpointModeError(protocol, mode);
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
function unsupportedNativeEndpointModeError(protocol, mode) {
|
|
4336
|
+
const callbackName = mode === "Produce" ? "Produce" : "Consume";
|
|
4337
|
+
const asyncCallbackName = `${callbackName}Async`;
|
|
4338
|
+
return new Error(`Native ${protocol} ${mode} mode is not supported by the TypeScript SDK. `
|
|
4339
|
+
+ `Provide a ${callbackName} or ${asyncCallbackName} callback instead.`);
|
|
4340
|
+
}
|
|
3202
4341
|
function validateHttpAuthOptions(options) {
|
|
3203
4342
|
const auth = options.auth;
|
|
3204
4343
|
if (!auth) {
|
|
@@ -3373,13 +4512,13 @@ function validatePushDiffusionEndpoint(endpoint, mode) {
|
|
|
3373
4512
|
}
|
|
3374
4513
|
requireNonEmptyString(optionString(options, "ServerUrl", "serverUrl"), "ServerUrl must be provided for Push Diffusion endpoint.");
|
|
3375
4514
|
requireNonEmptyString(optionString(options, "TopicPath", "topicPath"), "TopicPath must be provided for Push Diffusion endpoint.");
|
|
3376
|
-
|
|
4515
|
+
const callbacks = mode === "Produce"
|
|
4516
|
+
? selectProduceCallbackGroup(endpoint)
|
|
4517
|
+
: selectConsumeCallbackGroup(endpoint);
|
|
4518
|
+
if (mode === "Produce" && !callbacks.asyncDelegate && !callbacks.syncDelegate) {
|
|
3377
4519
|
throw new Error("PublishAsync delegate must be provided for Push Diffusion producer endpoint.");
|
|
3378
4520
|
}
|
|
3379
|
-
if (mode === "Consume"
|
|
3380
|
-
&& !resolveStructuredConsumeDelegate(endpoint)
|
|
3381
|
-
&& !resolveStructuredConsumeStreamDelegate(endpoint)
|
|
3382
|
-
&& !resolveConsumeDelegate(endpoint)) {
|
|
4521
|
+
if (mode === "Consume" && !callbacks.asyncDelegate && !callbacks.syncDelegate) {
|
|
3383
4522
|
throw new Error("SubscribeAsync delegate must be provided for Push Diffusion consumer endpoint.");
|
|
3384
4523
|
}
|
|
3385
4524
|
}
|
|
@@ -4120,60 +5259,109 @@ function parseBodyObject(body) {
|
|
|
4120
5259
|
return null;
|
|
4121
5260
|
}
|
|
4122
5261
|
}
|
|
5262
|
+
function selectProduceCallbackGroup(endpoint) {
|
|
5263
|
+
const genericAsync = endpoint.delegate?.produceAsync;
|
|
5264
|
+
const genericSync = endpoint.delegate?.produce;
|
|
5265
|
+
if (typeof genericAsync === "function" || typeof genericSync === "function") {
|
|
5266
|
+
return {
|
|
5267
|
+
asyncDelegate: typeof genericAsync === "function" ? genericAsync : undefined,
|
|
5268
|
+
syncDelegate: typeof genericSync === "function" ? genericSync : undefined
|
|
5269
|
+
};
|
|
5270
|
+
}
|
|
5271
|
+
let activeAsync;
|
|
5272
|
+
let activeSync;
|
|
5273
|
+
if (endpoint.kind === "PushDiffusion") {
|
|
5274
|
+
activeAsync = endpoint.pushDiffusion?.PublishAsync
|
|
5275
|
+
?? endpoint.pushDiffusion?.publishAsync;
|
|
5276
|
+
}
|
|
5277
|
+
else if (endpoint.kind === "Grpc") {
|
|
5278
|
+
activeAsync = endpoint.grpc?.ProduceAsync
|
|
5279
|
+
?? endpoint.grpc?.produceAsync;
|
|
5280
|
+
activeSync = endpoint.grpc?.Produce
|
|
5281
|
+
?? endpoint.grpc?.produce;
|
|
5282
|
+
}
|
|
5283
|
+
else if (endpoint.kind === "WebSocket") {
|
|
5284
|
+
activeAsync = endpoint.webSocket?.ProduceAsync
|
|
5285
|
+
?? endpoint.webSocket?.produceAsync;
|
|
5286
|
+
activeSync = endpoint.webSocket?.Produce
|
|
5287
|
+
?? endpoint.webSocket?.produce;
|
|
5288
|
+
}
|
|
5289
|
+
return {
|
|
5290
|
+
asyncDelegate: typeof activeAsync === "function"
|
|
5291
|
+
? activeAsync
|
|
5292
|
+
: undefined,
|
|
5293
|
+
syncDelegate: typeof activeSync === "function"
|
|
5294
|
+
? activeSync
|
|
5295
|
+
: undefined
|
|
5296
|
+
};
|
|
5297
|
+
}
|
|
5298
|
+
function selectConsumeCallbackGroup(endpoint) {
|
|
5299
|
+
const genericAsync = endpoint.delegate?.consumeAsync;
|
|
5300
|
+
const genericSync = endpoint.delegate?.consume;
|
|
5301
|
+
if (typeof genericAsync === "function" || typeof genericSync === "function") {
|
|
5302
|
+
return {
|
|
5303
|
+
asyncDelegate: typeof genericAsync === "function" ? genericAsync : undefined,
|
|
5304
|
+
syncDelegate: typeof genericSync === "function" ? genericSync : undefined
|
|
5305
|
+
};
|
|
5306
|
+
}
|
|
5307
|
+
let activeAsync;
|
|
5308
|
+
let activeSync;
|
|
5309
|
+
if (endpoint.kind === "PushDiffusion") {
|
|
5310
|
+
activeAsync = endpoint.pushDiffusion?.SubscribeAsync
|
|
5311
|
+
?? endpoint.pushDiffusion?.subscribeAsync;
|
|
5312
|
+
}
|
|
5313
|
+
else if (endpoint.kind === "Grpc") {
|
|
5314
|
+
activeAsync = endpoint.grpc?.ConsumeAsync
|
|
5315
|
+
?? endpoint.grpc?.consumeAsync;
|
|
5316
|
+
activeSync = endpoint.grpc?.Consume
|
|
5317
|
+
?? endpoint.grpc?.consume;
|
|
5318
|
+
}
|
|
5319
|
+
else if (endpoint.kind === "WebSocket") {
|
|
5320
|
+
activeAsync = endpoint.webSocket?.ConsumeAsync
|
|
5321
|
+
?? endpoint.webSocket?.consumeAsync;
|
|
5322
|
+
activeSync = endpoint.webSocket?.Consume
|
|
5323
|
+
?? endpoint.webSocket?.consume;
|
|
5324
|
+
}
|
|
5325
|
+
return {
|
|
5326
|
+
asyncDelegate: typeof activeAsync === "function" ? activeAsync : undefined,
|
|
5327
|
+
syncDelegate: typeof activeSync === "function"
|
|
5328
|
+
? activeSync
|
|
5329
|
+
: undefined
|
|
5330
|
+
};
|
|
5331
|
+
}
|
|
4123
5332
|
function resolveStructuredProduceDelegate(endpoint) {
|
|
4124
|
-
return endpoint.
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
?? endpoint.grpc?.produceAsync
|
|
4129
|
-
?? endpoint.webSocket?.ProduceAsync
|
|
4130
|
-
?? endpoint.webSocket?.produceAsync;
|
|
5333
|
+
return selectProduceCallbackGroup(endpoint).asyncDelegate;
|
|
5334
|
+
}
|
|
5335
|
+
function resolveProduceDelegate(endpoint) {
|
|
5336
|
+
return selectProduceCallbackGroup(endpoint).syncDelegate;
|
|
4131
5337
|
}
|
|
4132
5338
|
function resolveStructuredConsumeDelegate(endpoint) {
|
|
4133
|
-
const delegate = endpoint.
|
|
4134
|
-
?? endpoint.pushDiffusion?.SubscribeAsync
|
|
4135
|
-
?? endpoint.pushDiffusion?.subscribeAsync
|
|
4136
|
-
?? endpoint.grpc?.ConsumeAsync
|
|
4137
|
-
?? endpoint.grpc?.consumeAsync
|
|
4138
|
-
?? endpoint.webSocket?.ConsumeAsync
|
|
4139
|
-
?? endpoint.webSocket?.consumeAsync;
|
|
5339
|
+
const delegate = selectConsumeCallbackGroup(endpoint).asyncDelegate;
|
|
4140
5340
|
return typeof delegate === "function" && delegate.length === 0
|
|
4141
5341
|
? delegate
|
|
4142
5342
|
: undefined;
|
|
4143
5343
|
}
|
|
4144
5344
|
function resolveStructuredConsumeStreamDelegate(endpoint) {
|
|
4145
|
-
const delegate = endpoint.
|
|
4146
|
-
?? endpoint.pushDiffusion?.SubscribeAsync
|
|
4147
|
-
?? endpoint.pushDiffusion?.subscribeAsync
|
|
4148
|
-
?? endpoint.grpc?.ConsumeAsync
|
|
4149
|
-
?? endpoint.grpc?.consumeAsync
|
|
4150
|
-
?? endpoint.webSocket?.ConsumeAsync
|
|
4151
|
-
?? endpoint.webSocket?.consumeAsync;
|
|
5345
|
+
const delegate = selectConsumeCallbackGroup(endpoint).asyncDelegate;
|
|
4152
5346
|
return typeof delegate === "function" && delegate.length > 0
|
|
4153
5347
|
? delegate
|
|
4154
5348
|
: undefined;
|
|
4155
5349
|
}
|
|
4156
|
-
function resolveProduceDelegate(endpoint) {
|
|
4157
|
-
return endpoint.delegate?.produce
|
|
4158
|
-
?? endpoint.grpc?.Produce
|
|
4159
|
-
?? endpoint.grpc?.produce
|
|
4160
|
-
?? endpoint.webSocket?.Produce
|
|
4161
|
-
?? endpoint.webSocket?.produce;
|
|
4162
|
-
}
|
|
4163
5350
|
function resolveConsumeDelegate(endpoint) {
|
|
4164
|
-
return endpoint.
|
|
4165
|
-
?? endpoint.grpc?.Consume
|
|
4166
|
-
?? endpoint.grpc?.consume
|
|
4167
|
-
?? endpoint.webSocket?.Consume
|
|
4168
|
-
?? endpoint.webSocket?.consume;
|
|
5351
|
+
return selectConsumeCallbackGroup(endpoint).syncDelegate;
|
|
4169
5352
|
}
|
|
4170
5353
|
function resolveConnectionMetadata(endpoint) {
|
|
5354
|
+
const activeProtocolMetadata = endpoint.kind === "PushDiffusion"
|
|
5355
|
+
? toStringRecord(endpoint.pushDiffusion?.ConnectionProperties ?? endpoint.pushDiffusion?.connectionProperties)
|
|
5356
|
+
: endpoint.kind === "Grpc"
|
|
5357
|
+
? toStringRecord(endpoint.grpc?.ConnectionMetadata ?? endpoint.grpc?.connectionMetadata)
|
|
5358
|
+
: endpoint.kind === "WebSocket"
|
|
5359
|
+
? toStringRecord(endpoint.webSocket?.ConnectionMetadata ?? endpoint.webSocket?.connectionMetadata)
|
|
5360
|
+
: {};
|
|
4171
5361
|
return {
|
|
5362
|
+
...activeProtocolMetadata,
|
|
4172
5363
|
...(endpoint.connectionMetadata ?? {}),
|
|
4173
|
-
...(endpoint.delegate?.connectionMetadata ?? {})
|
|
4174
|
-
...toStringRecord(endpoint.pushDiffusion?.ConnectionProperties ?? endpoint.pushDiffusion?.connectionProperties),
|
|
4175
|
-
...toStringRecord(endpoint.grpc?.ConnectionMetadata ?? endpoint.grpc?.connectionMetadata),
|
|
4176
|
-
...toStringRecord(endpoint.webSocket?.ConnectionMetadata ?? endpoint.webSocket?.connectionMetadata)
|
|
5364
|
+
...(endpoint.delegate?.connectionMetadata ?? {})
|
|
4177
5365
|
};
|
|
4178
5366
|
}
|
|
4179
5367
|
function isStructuredProduceResult(value) {
|