Merge remote-tracking branch 'origin/release/2.1' into dev

This commit is contained in:
BrennanConroy 2018-03-22 15:57:47 -07:00
commit fd54ec2c4f
109 changed files with 536 additions and 135 deletions

View File

@ -15,7 +15,7 @@ This project is part of ASP.NET Core. You can find samples, documentation and ge
If you are encountering TypeScript definition issues with SignalR, please ensure you are using the latest version of TypeScript to compile your application. If the issue occurs in the latest TypeScript, please let us know.
When in doubt, check the version of TypeScript referenced by our [package.json](client-ts/package.json) file. That version is the minimum TypeScript version expected to work with SignalR.
When in doubt, check the version of TypeScript referenced by our [package.json](clients/ts/package.json) file. That version is the minimum TypeScript version expected to work with SignalR.
## Packages

View File

@ -51,7 +51,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Signal
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SignalR.Client.FunctionalTests", "test\Microsoft.AspNetCore.SignalR.Client.FunctionalTests\Microsoft.AspNetCore.SignalR.Client.FunctionalTests.csproj", "{455B68D2-C5B6-4BF4-A685-964B07AFAAF8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "client-ts", "client-ts", "{3A76C5A2-79ED-49BC-8BDC-6A3A766FFA1B}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "clients", "clients", "{3A76C5A2-79ED-49BC-8BDC-6A3A766FFA1B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SignalR.Microbenchmarks", "benchmarks\Microsoft.AspNetCore.SignalR.Microbenchmarks\Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj", "{96771B3F-4D18-41A7-A75B-FF38E76AAC89}"
EndProject
@ -85,7 +85,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Signal
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SignalR.Client.MsgPack", "src\Microsoft.AspNetCore.SignalR.Client.MsgPack\Microsoft.AspNetCore.SignalR.Client.MsgPack.csproj", "{4DBF918E-BD37-4309-B448-BA68C935944D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "client-ts\FunctionalTests\FunctionalTests.csproj", "{D0C7B22E-B0B6-4D62-BF7D-79EE4AAF1981}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "clients\ts\FunctionalTests\FunctionalTests.csproj", "{D0C7B22E-B0B6-4D62-BF7D-79EE4AAF1981}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkServer", "benchmarks\BenchmarkServer\BenchmarkServer.csproj", "{B5286020-C218-443C-91A9-B65751FB9B29}"
EndProject

View File

@ -50,8 +50,14 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
public class FakeHubProtocol : IHubProtocol
{
public string Name { get; }
public int Version => 1;
public TransferFormat TransferFormat { get; }
public bool IsVersionSupported(int version)
{
return true;
}
public bool TryParseMessages(ReadOnlyMemory<byte> input, IInvocationBinder binder, IList<HubMessage> messages)
{
return false;

View File

@ -1,10 +1,10 @@
<Project>
<ItemGroup>
<NPMPackage Include="$(RepositoryRoot)client-ts/signalr">
<NPMPackage Include="$(RepositoryRoot)clients/ts/signalr">
<TarName>aspnet-signalr</TarName>
<PackageId>@aspnet/signalr</PackageId>
</NPMPackage>
<NPMPackage Include="$(RepositoryRoot)client-ts/signalr-protocol-msgpack">
<NPMPackage Include="$(RepositoryRoot)clients/ts/signalr-protocol-msgpack">
<TarName>aspnet-signalr-protocol-msgpack</TarName>
<PackageId>@aspnet/signalr-protocol-msgpack</PackageId>
</NPMPackage>
@ -15,11 +15,11 @@
</PropertyGroup>
<Target Name="RestoreNpm" Condition="'$(PreflightRestore)' != 'True'">
<Message Text="Restoring NPM modules" Importance="high" />
<Exec Command="npm install --no-optional" WorkingDirectory="$(RepositoryRoot)client-ts" />
<Exec Command="npm install --no-optional" WorkingDirectory="$(RepositoryRoot)client-ts/FunctionalTests" />
<Exec Command="npm install --no-optional" WorkingDirectory="$(RepositoryRoot)client-ts/signalr" />
<Exec Command="npm install --no-optional" WorkingDirectory="$(RepositoryRoot)client-ts/signalr-protocol-msgpack" />
<Exec Command="npm install --no-optional" WorkingDirectory="$(RepositoryRoot)client-ts/webdriver-tap-runner" />
<Exec Command="npm install --no-optional" WorkingDirectory="$(RepositoryRoot)clients/ts" />
<Exec Command="npm install --no-optional" WorkingDirectory="$(RepositoryRoot)clients/ts/FunctionalTests" />
<Exec Command="npm install --no-optional" WorkingDirectory="$(RepositoryRoot)clients/ts/signalr" />
<Exec Command="npm install --no-optional" WorkingDirectory="$(RepositoryRoot)clients/ts/signalr-protocol-msgpack" />
<Exec Command="npm install --no-optional" WorkingDirectory="$(RepositoryRoot)clients/ts/webdriver-tap-runner" />
</Target>
<PropertyGroup>
@ -28,12 +28,12 @@
<Target Name="RunTSClientNodeTests">
<Message Text="Running TypeScript client Node tests" Importance="high" />
<Exec Command="npm test" WorkingDirectory="$(RepositoryRoot)client-ts" IgnoreStandardErrorWarningFormat="true" />
<Exec Command="npm test" WorkingDirectory="$(RepositoryRoot)clients/ts" IgnoreStandardErrorWarningFormat="true" />
</Target>
<Target Name="RunBrowserTests">
<Message Text="Running TypeScript client Browser tests" Importance="high" />
<Exec Command="npm run ci-test -- --configuration $(Configuration) -v" WorkingDirectory="$(RepositoryRoot)client-ts/FunctionalTests" IgnoreStandardErrorWarningFormat="true" />
<Exec Command="npm run ci-test -- --configuration $(Configuration) -v" WorkingDirectory="$(RepositoryRoot)clients/ts/FunctionalTests" IgnoreStandardErrorWarningFormat="true" />
</Target>
<PropertyGroup>

View File

@ -1,34 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { HubMessage, IHubProtocol } from "./IHubProtocol";
import { TextMessageFormat } from "./TextMessageFormat";
import { TransferFormat } from "./Transports";
export const JSON_HUB_PROTOCOL_NAME: string = "json";
export class JsonHubProtocol implements IHubProtocol {
public readonly name: string = JSON_HUB_PROTOCOL_NAME;
public readonly transferFormat: TransferFormat = TransferFormat.Text;
public parseMessages(input: string): HubMessage[] {
if (!input) {
return [];
}
// Parse the messages
const messages = TextMessageFormat.parse(input);
const hubMessages = [];
for (const message of messages) {
hubMessages.push(JSON.parse(message));
}
return hubMessages;
}
public writeMessage(message: HubMessage): string {
return TextMessageFormat.write(JSON.stringify(message));
}
}

View File

@ -6,8 +6,8 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.SignalR.MsgPack\Microsoft.AspNetCore.SignalR.MsgPack.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.SignalR\Microsoft.AspNetCore.SignalR.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.SignalR.MsgPack\Microsoft.AspNetCore.SignalR.MsgPack.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.SignalR\Microsoft.AspNetCore.SignalR.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -18,7 +18,7 @@
"build:cjs": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module commonjs --outDir ./dist/cjs --target ES5",
"build:browser": "node ../node_modules/rollup/bin/rollup -c",
"build:uglify": "node ../node_modules/uglify-js/bin/uglifyjs --source-map \"url='signalr-protocol-msgpack.min.js.map',content='./dist/browser/signalr-protocol-msgpack.js.map'\" --comments -o ./dist/browser/signalr-protocol-msgpack.min.js ./dist/browser/signalr-protocol-msgpack.js",
"pretest": "node ../node_modules/rimraf/bin.js ./spec/obj && node ../node_modules/typescript/bin/tsc --project ./spec/tsconfig.json && cd ./spec/obj/src && npm init -y && npm install ../../../../signalr",
"pretest": "node ../node_modules/rimraf/bin.js ./spec/obj && node ../node_modules/typescript/bin/tsc --project ./spec/tsconfig.json && cd ./spec/obj && npm init -y && npm install ../../../signalr",
"test": "node ../node_modules/jasmine/bin/jasmine.js ./spec/obj/spec/**/*.spec.js"
},
"keywords": [

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { CompletionMessage, InvocationMessage, MessageType, StreamItemMessage } from "@aspnet/signalr";
import { CompletionMessage, InvocationMessage, MessageType, NullLogger, StreamItemMessage } from "@aspnet/signalr";
import { MessagePackHubProtocol } from "../src/MessagePackHubProtocol";
describe("MessageHubProtocol", () => {
@ -14,7 +14,7 @@ describe("MessageHubProtocol", () => {
} as InvocationMessage;
const protocol = new MessagePackHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation));
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), new NullLogger());
expect(parsedMessages).toEqual([invocation]);
});
@ -27,7 +27,7 @@ describe("MessageHubProtocol", () => {
} as InvocationMessage;
const protocol = new MessagePackHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation));
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), new NullLogger());
expect(parsedMessages).toEqual([invocation]);
});
@ -42,7 +42,7 @@ describe("MessageHubProtocol", () => {
} as InvocationMessage;
const protocol = new MessagePackHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation));
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), new NullLogger());
expect(parsedMessages).toEqual([invocation]);
});
@ -56,7 +56,7 @@ describe("MessageHubProtocol", () => {
} as InvocationMessage;
const protocol = new MessagePackHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation));
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), new NullLogger());
expect(parsedMessages).toEqual([invocation]);
});
@ -93,9 +93,18 @@ describe("MessageHubProtocol", () => {
result: new Date(Date.UTC(2018, 0, 1, 11, 24, 0)),
type: MessageType.Completion,
} as CompletionMessage],
// extra property at the end should be ignored (testing older protocol client working with newer protocol server)
[[0x09, 0x95, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x02, 0x00],
{
error: null,
headers: {},
invocationId: "abc",
result: null,
type: MessageType.Completion,
} as CompletionMessage],
] as Array<[number[], CompletionMessage]>).forEach(([payload, expectedMessage]) =>
it("can read Completion message", () => {
const messages = new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer);
const messages = new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer, new NullLogger());
expect(messages).toEqual([expectedMessage]);
}));
@ -113,10 +122,10 @@ describe("MessageHubProtocol", () => {
invocationId: "abc",
item: new Date(Date.UTC(2018, 0, 1, 11, 24, 0)),
type: MessageType.StreamItem,
} as StreamItemMessage]
} as StreamItemMessage],
] as Array<[number[], StreamItemMessage]>).forEach(([payload, expectedMessage]) =>
it("can read StreamItem message", () => {
const messages = new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer);
const messages = new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer, new NullLogger());
expect(messages).toEqual([expectedMessage]);
}));
@ -132,7 +141,7 @@ describe("MessageHubProtocol", () => {
} as StreamItemMessage],
] as Array<[number[], StreamItemMessage]>).forEach(([payload, expectedMessage]) =>
it("can read message with headers", () => {
const messages = new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer);
const messages = new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer, new NullLogger());
expect(messages).toEqual([expectedMessage]);
}));
@ -140,18 +149,15 @@ describe("MessageHubProtocol", () => {
["message with no payload", [0x00], new Error("Invalid payload.")],
["message with empty array", [0x01, 0x90], new Error("Invalid payload.")],
["message without outer array", [0x01, 0xc2], new Error("Invalid payload.")],
["message with out-of-range message type", [0x03, 0x92, 0x05, 0x80], new Error("Invalid message type.")],
["message with non-integer message type", [0x04, 0x92, 0xa1, 0x78, 0x80], new Error("Invalid message type.")],
["message with invalid headers", [0x03, 0x92, 0x01, 0x05], new Error("Invalid headers.")],
["Invocation message with invalid invocation id", [0x03, 0x92, 0x01, 0x80], new Error("Invalid payload for Invocation message.")],
["StreamItem message with invalid invocation id", [0x03, 0x92, 0x02, 0x80], new Error("Invalid payload for stream Result message.")],
["StreamItem message with invalid invocation id", [0x03, 0x92, 0x02, 0x80], new Error("Invalid payload for StreamItem message.")],
["Completion message with invalid invocation id", [0x04, 0x93, 0x03, 0x80, 0xa0], new Error("Invalid payload for Completion message.")],
["Completion message with unexpected result", [0x06, 0x95, 0x03, 0x80, 0xa0, 0x02, 0x00], new Error("Invalid payload for Completion message.")],
["Completion message with missing result", [0x05, 0x94, 0x03, 0x80, 0xa0, 0x01], new Error("Invalid payload for Completion message.")],
["Completion message with missing error", [0x05, 0x94, 0x03, 0x80, 0xa0, 0x03], new Error("Invalid payload for Completion message.")],
] as Array<[string, number[], Error]>).forEach(([name, payload, expectedError]) =>
it("throws for " + name, () => {
expect(() => new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer))
expect(() => new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer, new NullLogger()))
.toThrow(expectedError);
}));
@ -159,7 +165,7 @@ describe("MessageHubProtocol", () => {
const payload = [
0x08, 0x94, 0x02, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x08,
0x0b, 0x95, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x03, 0xa2, 0x4f, 0x4b];
const messages = new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer);
const messages = new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer, new NullLogger());
expect(messages).toEqual([
{
headers: {},
@ -183,7 +189,7 @@ describe("MessageHubProtocol", () => {
0x91, // message array length = 1 (fixarray)
0x06, // type = 6 = Ping (fixnum)
];
const messages = new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer);
const messages = new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer, new NullLogger());
expect(messages).toEqual([
{
type: MessageType.Ping,

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { CompletionMessage, HubMessage, IHubProtocol, InvocationMessage, MessageHeaders, MessageType, StreamInvocationMessage, StreamItemMessage, TransferFormat } from "@aspnet/signalr";
import { CompletionMessage, HubMessage, IHubProtocol, ILogger, InvocationMessage, LogLevel, MessageHeaders, MessageType, NullLogger, StreamInvocationMessage, StreamItemMessage, TransferFormat } from "@aspnet/signalr";
import { Buffer } from "buffer";
import * as msgpack5 from "msgpack5";
import { BinaryMessageFormat } from "./BinaryMessageFormat";
@ -9,14 +9,18 @@ import { BinaryMessageFormat } from "./BinaryMessageFormat";
export class MessagePackHubProtocol implements IHubProtocol {
public readonly name: string = "messagepack";
public readonly version: number = 1;
public readonly transferFormat: TransferFormat = TransferFormat.Binary;
public parseMessages(input: ArrayBuffer): HubMessage[] {
return BinaryMessageFormat.parse(input).map((m) => this.parseMessage(m));
public parseMessages(input: ArrayBuffer, logger: ILogger): HubMessage[] {
if (logger === null) {
logger = new NullLogger();
}
return BinaryMessageFormat.parse(input).map((m) => this.parseMessage(m, logger));
}
private parseMessage(input: Uint8Array): HubMessage {
private parseMessage(input: Uint8Array, logger: ILogger): HubMessage {
if (input.length === 0) {
throw new Error("Invalid payload.");
}
@ -41,12 +45,15 @@ export class MessagePackHubProtocol implements IHubProtocol {
case MessageType.Close:
return this.createCloseMessage(properties);
default:
throw new Error("Invalid message type.");
// Future protocol changes can add message types, old clients can ignore them
logger.log(LogLevel.Information, "Unknown message type '" + messageType + "' ignored.");
return null;
}
}
private createCloseMessage(properties: any[]): HubMessage {
if (properties.length !== 2) {
// check minimum length to allow protocol to add items to the end of objects in future releases
if (properties.length < 2) {
throw new Error("Invalid payload for Close message.");
}
@ -58,7 +65,8 @@ export class MessagePackHubProtocol implements IHubProtocol {
}
private createPingMessage(properties: any[]): HubMessage {
if (properties.length !== 1) {
// check minimum length to allow protocol to add items to the end of objects in future releases
if (properties.length < 1) {
throw new Error("Invalid payload for Ping message.");
}
@ -69,7 +77,8 @@ export class MessagePackHubProtocol implements IHubProtocol {
}
private createInvocationMessage(headers: MessageHeaders, properties: any[]): InvocationMessage {
if (properties.length !== 5) {
// check minimum length to allow protocol to add items to the end of objects in future releases
if (properties.length < 5) {
throw new Error("Invalid payload for Invocation message.");
}
@ -94,8 +103,9 @@ export class MessagePackHubProtocol implements IHubProtocol {
}
private createStreamItemMessage(headers: MessageHeaders, properties: any[]): StreamItemMessage {
if (properties.length !== 4) {
throw new Error("Invalid payload for stream Result message.");
// check minimum length to allow protocol to add items to the end of objects in future releases
if (properties.length < 4) {
throw new Error("Invalid payload for StreamItem message.");
}
return {
@ -107,6 +117,7 @@ export class MessagePackHubProtocol implements IHubProtocol {
}
private createCompletionMessage(headers: MessageHeaders, properties: any[]): CompletionMessage {
// check minimum length to allow protocol to add items to the end of objects in future releases
if (properties.length < 4) {
throw new Error("Invalid payload for Completion message.");
}
@ -117,8 +128,7 @@ export class MessagePackHubProtocol implements IHubProtocol {
const resultKind = properties[3];
if ((resultKind === voidResult && properties.length !== 4) ||
(resultKind !== voidResult && properties.length !== 5)) {
if (resultKind !== voidResult && properties.length < 5) {
throw new Error("Invalid payload for Completion message.");
}

View File

@ -4,7 +4,7 @@
import { ConnectionClosed, DataReceived } from "../src/Common";
import { HubConnection } from "../src/HubConnection";
import { IConnection } from "../src/IConnection";
import { MessageType, IHubProtocol, HubMessage } from "../src/IHubProtocol";
import { HubMessage, IHubProtocol, MessageType } from "../src/IHubProtocol";
import { ILogger, LogLevel } from "../src/ILogger";
import { Observer } from "../src/Observable";
import { TextMessageFormat } from "../src/TextMessageFormat";
@ -27,6 +27,7 @@ describe("HubConnection", () => {
expect(connection.sentData.length).toBe(1);
expect(JSON.parse(connection.sentData[0])).toEqual({
protocol: "json",
version: 1,
});
await hubConnection.stop();
});
@ -203,7 +204,7 @@ describe("HubConnection", () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
connection.receiveHandshakeResponse();
const invokePromise = hubConnection.invoke("testMethod");
@ -232,7 +233,7 @@ describe("HubConnection", () => {
connection.receive({
arguments: ["test"],
invocationId: 0,
invocationId: "0",
nonblocking: true,
target: "message",
type: MessageType.Invocation,
@ -240,7 +241,7 @@ describe("HubConnection", () => {
expect(warnings).toEqual(["No client method with the name 'message' found."]);
});
it("invocations ignored in callbacks that have registered then unregistered", async () => {
const warnings: string[] = [];
const logger = {
@ -261,7 +262,7 @@ describe("HubConnection", () => {
connection.receive({
arguments: ["test"],
invocationId: 0,
invocationId: "0",
nonblocking: true,
target: "message",
type: MessageType.Invocation,
@ -273,7 +274,7 @@ describe("HubConnection", () => {
it("callback invoked when servers invokes a method on the client", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
connection.receiveHandshakeResponse();
let value = "";
@ -281,7 +282,7 @@ describe("HubConnection", () => {
connection.receive({
arguments: ["test"],
invocationId: 0,
invocationId: "0",
nonblocking: true,
target: "message",
type: MessageType.Invocation,
@ -293,7 +294,7 @@ describe("HubConnection", () => {
it("stop on handshake error", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
let closeError: Error = null;
hubConnection.onclose((e) => closeError = e);
@ -305,7 +306,7 @@ describe("HubConnection", () => {
it("stop on close message", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
let isClosed = false;
let closeError: Error = null;
hubConnection.onclose((e) => {
@ -326,7 +327,7 @@ describe("HubConnection", () => {
it("stop on error close message", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
let isClosed = false;
let closeError: Error = null;
hubConnection.onclose((e) => {
@ -348,7 +349,7 @@ describe("HubConnection", () => {
it("can have multiple callbacks", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
connection.receiveHandshakeResponse();
let numInvocations1 = 0;
@ -358,7 +359,7 @@ describe("HubConnection", () => {
connection.receive({
arguments: [],
invocationId: 0,
invocationId: "0",
nonblocking: true,
target: "message",
type: MessageType.Invocation,
@ -380,7 +381,7 @@ describe("HubConnection", () => {
connection.receive({
arguments: [],
invocationId: 0,
invocationId: "0",
nonblocking: true,
target: "message",
type: MessageType.Invocation,
@ -390,7 +391,7 @@ describe("HubConnection", () => {
connection.receive({
arguments: [],
invocationId: 0,
invocationId: "0",
nonblocking: true,
target: "message",
type: MessageType.Invocation,
@ -434,7 +435,7 @@ describe("HubConnection", () => {
// invoke a method to make sure we are not trying to use null/undefined
connection.receive({
arguments: [],
invocationId: 0,
invocationId: "0",
nonblocking: true,
target: "message",
type: MessageType.Invocation,
@ -493,7 +494,7 @@ describe("HubConnection", () => {
it("completes the observer when a completion is received", async () => {
const connection = new TestConnection();
const hubConnection = new HubConnection(connection, commonOptions);
connection.receiveHandshakeResponse();
const observer = new TestObserver();
@ -779,6 +780,7 @@ class TestConnection implements IConnection {
class TestProtocol implements IHubProtocol {
public readonly name: string = "TestProtocol";
public readonly version: number = 1;
public readonly transferFormat: TransferFormat;

View File

@ -0,0 +1,196 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { CompletionMessage, InvocationMessage, MessageType, StreamItemMessage } from "../src/IHubProtocol";
import { JsonHubProtocol } from "../src/JsonHubProtocol";
import { NullLogger } from "../src/Loggers";
import { TextMessageFormat } from "../src/TextMessageFormat";
describe("JsonHubProtocol", () => {
it("can write/read non-blocking Invocation message", () => {
const invocation = {
arguments: [42, true, "test", ["x1", "y2"], null],
headers: {},
target: "myMethod",
type: MessageType.Invocation,
} as InvocationMessage;
const protocol = new JsonHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), new NullLogger());
expect(parsedMessages).toEqual([invocation]);
});
it("can read Invocation message with Date argument", () => {
const invocation = {
arguments: [Date.UTC(2018, 1, 1, 12, 34, 56)],
headers: {},
target: "mymethod",
type: MessageType.Invocation,
} as InvocationMessage;
const protocol = new JsonHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), new NullLogger());
expect(parsedMessages).toEqual([invocation]);
});
it("can write/read Invocation message with headers", () => {
const invocation = {
arguments: [42, true, "test", ["x1", "y2"], null],
headers: {
foo: "bar",
},
target: "myMethod",
type: MessageType.Invocation,
} as InvocationMessage;
const protocol = new JsonHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), new NullLogger());
expect(parsedMessages).toEqual([invocation]);
});
it("can write/read Invocation message", () => {
const invocation = {
arguments: [42, true, "test", ["x1", "y2"], null],
headers: {},
invocationId: "123",
target: "myMethod",
type: MessageType.Invocation,
} as InvocationMessage;
const protocol = new JsonHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), new NullLogger());
expect(parsedMessages).toEqual([invocation]);
});
([
[`{"type":3, "invocationId": "abc", "error": "Err", "result": null, "headers": {}}${TextMessageFormat.RecordSeparator}`,
{
error: "Err",
headers: {},
invocationId: "abc",
result: null,
type: MessageType.Completion,
} as CompletionMessage],
[`{"type":3, "invocationId": "abc", "result": "OK", "error": null, "headers": {}}${TextMessageFormat.RecordSeparator}`,
{
error: null,
headers: {},
invocationId: "abc",
result: "OK",
type: MessageType.Completion,
} as CompletionMessage],
[`{"type":3, "invocationId": "abc", "error": null, "result": null, "headers": {}}${TextMessageFormat.RecordSeparator}`,
{
error: null,
headers: {},
invocationId: "abc",
result: null,
type: MessageType.Completion,
} as CompletionMessage],
[`{"type":3, "invocationId": "abc", "result": 1514805840000, "error": null, "headers": {}}${TextMessageFormat.RecordSeparator}`,
{
error: null,
headers: {},
invocationId: "abc",
result: Date.UTC(2018, 0, 1, 11, 24, 0),
type: MessageType.Completion,
} as CompletionMessage],
[`{"type":3, "invocationId": "abc", "error": null, "result": null, "headers": {}, "extraParameter":"value"}${TextMessageFormat.RecordSeparator}`,
{
error: null,
extraParameter: "value",
headers: {},
invocationId: "abc",
result: null,
type: MessageType.Completion,
} as CompletionMessage],
] as Array<[string, CompletionMessage]>).forEach(([payload, expectedMessage]) =>
it("can read Completion message", () => {
const messages = new JsonHubProtocol().parseMessages(payload, new NullLogger());
expect(messages).toEqual([expectedMessage]);
}));
([
[`{"type":2, "invocationId": "abc", "headers": {}, "item": 8}${TextMessageFormat.RecordSeparator}`,
{
headers: {},
invocationId: "abc",
item: 8,
type: MessageType.StreamItem,
} as StreamItemMessage],
[`{"type":2, "invocationId": "abc", "headers": {}, "item": 1514805840000}${TextMessageFormat.RecordSeparator}`,
{
headers: {},
invocationId: "abc",
item: Date.UTC(2018, 0, 1, 11, 24, 0),
type: MessageType.StreamItem,
} as StreamItemMessage],
] as Array<[string, StreamItemMessage]>).forEach(([payload, expectedMessage]) =>
it("can read StreamItem message", () => {
const messages = new JsonHubProtocol().parseMessages(payload, new NullLogger());
expect(messages).toEqual([expectedMessage]);
}));
([
[`{"type":2, "invocationId": "abc", "headers": {"t": "u"}, "item": 8}${TextMessageFormat.RecordSeparator}`,
{
headers: {
t: "u",
},
invocationId: "abc",
item: 8,
type: MessageType.StreamItem,
} as StreamItemMessage],
] as Array<[string, StreamItemMessage]>).forEach(([payload, expectedMessage]) =>
it("can read message with headers", () => {
const messages = new JsonHubProtocol().parseMessages(payload, new NullLogger());
expect(messages).toEqual([expectedMessage]);
}));
([
["message with empty payload", `{}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload.")],
["Invocation message with invalid invocation id", `{"type":1,"invocationId":1,"target":"method"}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Invocation message.")],
["Invocation message with empty string invocation id", `{"type":1,"invocationId":"","target":"method"}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Invocation message.")],
["Invocation message with invalid target", `{"type":1,"invocationId":"1","target":1}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Invocation message.")],
["StreamItem message with missing invocation id", `{"type":2}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for StreamItem message.")],
["StreamItem message with invalid invocation id", `{"type":2,"invocationId":1}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for StreamItem message.")],
["Completion message with missing invocation id", `{"type":3}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Completion message.")],
["Completion message with invalid invocation id", `{"type":3,"invocationId":1}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Completion message.")],
["Completion message with result and error", `{"type":3,"invocationId":"1","result":2,"error":"error"}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Completion message.")],
["Completion message with non-string error", `{"type":3,"invocationId":"1","error":21}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Completion message.")],
] as Array<[string, string, Error]>).forEach(([name, payload, expectedError]) =>
it("throws for " + name, () => {
expect(() => new JsonHubProtocol().parseMessages(payload, new NullLogger()))
.toThrow(expectedError);
}));
it("can read multiple messages", () => {
const payload = `{"type":2, "invocationId": "abc", "headers": {}, "item": 8}${TextMessageFormat.RecordSeparator}{"type":3, "invocationId": "abc", "headers": {}, "result": "OK", "error": null}${TextMessageFormat.RecordSeparator}`;
const messages = new JsonHubProtocol().parseMessages(payload, new NullLogger());
expect(messages).toEqual([
{
headers: {},
invocationId: "abc",
item: 8,
type: MessageType.StreamItem,
} as StreamItemMessage,
{
error: null,
headers: {},
invocationId: "abc",
result: "OK",
type: MessageType.Completion,
} as CompletionMessage,
]);
});
it("can read ping message", () => {
const payload = `{"type":6}${TextMessageFormat.RecordSeparator}`;
const messages = new JsonHubProtocol().parseMessages(payload, new NullLogger());
expect(messages).toEqual([
{
type: MessageType.Ping,
},
]);
});
});

View File

@ -70,7 +70,7 @@ export class HubConnection {
// Data may have all been read when processing handshake response
if (data) {
// Parse the messages
const messages = this.protocol.parseMessages(data);
const messages = this.protocol.parseMessages(data, this.logger);
for (const message of messages) {
switch (message.type) {
@ -211,7 +211,7 @@ export class HubConnection {
// Handshake request is always JSON
await this.connection.send(
TextMessageFormat.write(
JSON.stringify({ protocol: this.protocol.name } as HandshakeRequestMessage)));
JSON.stringify({ protocol: this.protocol.name, version: this.protocol.version } as HandshakeRequestMessage)));
this.logger.log(LogLevel.Information, `Using HubProtocol '${this.protocol.name}'.`);

View File

@ -1,4 +1,5 @@
import { TransferFormat } from "./Transports";
import { ILogger } from "./ILogger";
import { TransferFormat } from "./Transports";
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
@ -51,6 +52,7 @@ export interface CompletionMessage extends HubInvocationMessage {
export interface HandshakeRequestMessage {
readonly protocol: string;
readonly version: number;
}
export interface HandshakeResponseMessage {
@ -72,7 +74,8 @@ export interface CancelInvocationMessage extends HubInvocationMessage {
export interface IHubProtocol {
readonly name: string;
readonly version: number;
readonly transferFormat: TransferFormat;
parseMessages(input: any): HubMessage[];
parseMessages(input: any, logger: ILogger): HubMessage[];
writeMessage(message: HubMessage): any;
}

View File

@ -0,0 +1,101 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { CloseMessage, CompletionMessage, HubMessage, IHubProtocol, InvocationMessage, MessageType, PingMessage, StreamItemMessage } from "./IHubProtocol";
import { ILogger, LogLevel } from "./ILogger";
import { NullLogger } from "./Loggers";
import { TextMessageFormat } from "./TextMessageFormat";
import { TransferFormat } from "./Transports";
export const JSON_HUB_PROTOCOL_NAME: string = "json";
export class JsonHubProtocol implements IHubProtocol {
public readonly name: string = JSON_HUB_PROTOCOL_NAME;
public readonly version: number = 1;
public readonly transferFormat: TransferFormat = TransferFormat.Text;
public parseMessages(input: string, logger: ILogger): HubMessage[] {
if (!input) {
return [];
}
if (logger === null) {
logger = new NullLogger();
}
// Parse the messages
const messages = TextMessageFormat.parse(input);
const hubMessages = [];
for (const message of messages) {
const parsedMessage = JSON.parse(message) as HubMessage;
if (typeof parsedMessage.type !== "number") {
throw new Error("Invalid payload.");
}
switch (parsedMessage.type) {
case MessageType.Invocation:
this.isInvocationMessage(parsedMessage);
break;
case MessageType.StreamItem:
this.isStreamItemMessage(parsedMessage);
break;
case MessageType.Completion:
this.isCompletionMessage(parsedMessage);
break;
case MessageType.Ping:
// Single value, no need to validate
break;
case MessageType.Close:
// All optional values, no need to validate
break;
default:
// Future protocol changes can add message types, old clients can ignore them
logger.log(LogLevel.Information, "Unknown message type '" + parsedMessage.type + "' ignored.");
continue;
}
hubMessages.push(parsedMessage);
}
return hubMessages;
}
public writeMessage(message: HubMessage): string {
return TextMessageFormat.write(JSON.stringify(message));
}
private isInvocationMessage(message: InvocationMessage): void {
this.assertNotEmptyString(message.target, "Invalid payload for Invocation message.");
if (message.invocationId !== undefined) {
this.assertNotEmptyString(message.invocationId, "Invalid payload for Invocation message.");
}
}
private isStreamItemMessage(message: StreamItemMessage): void {
this.assertNotEmptyString(message.invocationId, "Invalid payload for StreamItem message.");
if (message.item === undefined) {
throw new Error("Invalid payload for StreamItem message.");
}
}
private isCompletionMessage(message: CompletionMessage): void {
if (message.result && message.error) {
throw new Error("Invalid payload for Completion message.");
}
if (!message.result && message.error) {
this.assertNotEmptyString(message.error, "Invalid payload for Completion message.");
}
this.assertNotEmptyString(message.invocationId, "Invalid payload for Completion message.");
}
private assertNotEmptyString(value: any, errorMessage: string): void {
if (typeof value !== "string" || value === "") {
throw new Error(errorMessage);
}
}
}

View File

@ -41,8 +41,8 @@
<Target Name="CopyTSClient" BeforeTargets="AfterBuild">
<ItemGroup>
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)..\..\client-ts\signalr\dist\browser\*" />
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)..\..\client-ts\signalr-protocol-msgpack\dist\browser\*" />
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)..\..\clients\ts\signalr\dist\browser\*" />
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)..\..\clients\ts\signalr-protocol-msgpack\dist\browser\*" />
</ItemGroup>
<Copy SourceFiles="@(SignalRJSClientFiles)" DestinationFolder="$(MSBuildThisFileDirectory)wwwroot\lib\signalr-client" />
</Target>

View File

@ -25,8 +25,8 @@
<Target Name="CopyTSClient" BeforeTargets="AfterBuild">
<ItemGroup>
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)..\..\client-ts\signalr\dist\browser\*" />
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)..\..\client-ts\signalr-protocol-msgpack\dist\browser\*" />
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)..\..\clients\ts\signalr\dist\browser\*" />
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)..\..\clients\ts\signalr-protocol-msgpack\dist\browser\*" />
</ItemGroup>
<Copy SourceFiles="@(SignalRJSClientFiles)" DestinationFolder="$(MSBuildThisFileDirectory)wwwroot\lib\signalr-client" />
</Target>

View File

@ -27,13 +27,13 @@
<Target Name="CopyTSClient" BeforeTargets="AfterBuild">
<ItemGroup>
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)..\..\client-ts\signalr\dist\browser\*" />
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)..\..\client-ts\signalr-protocol-msgpack\dist\browser\*" />
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)..\..\clients\ts\signalr\dist\browser\*" />
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)..\..\clients\ts\signalr-protocol-msgpack\dist\browser\*" />
</ItemGroup>
<Copy SourceFiles="@(SignalRJSClientFiles)" DestinationFolder="$(MSBuildThisFileDirectory)wwwroot\lib\signalr" />
<ItemGroup>
<MsgPackClientFiles Include="$(MSBuildThisFileDirectory)..\..\client-ts\signalr-protocol-msgpack\node_modules\msgpack5\dist\*" />
<MsgPackClientFiles Include="$(MSBuildThisFileDirectory)..\..\clients\ts\signalr-protocol-msgpack\node_modules\msgpack5\dist\*" />
</ItemGroup>
<Copy SourceFiles="@(MsgPackClientFiles)" DestinationFolder="$(MSBuildThisFileDirectory)wwwroot\lib\msgpack5" />
</Target>

View File

@ -32,23 +32,25 @@ In the SignalR protocol, the following types of messages can be sent:
| `CancelInvocation` | Caller | Sent by the client to cancel a streaming invocation on the server. |
| `Ping` | Caller, Callee | Sent by either party to check if the connection is active. |
After opening a connection to the server the client must send a `HandshakeRequest` message to the server as its first message. The handshake message is **always** a JSON message and contains the name of the format (protocol) that will be used for the duration of the connection. The server will reply with a `HandshakeResponse`, also always JSON, containing an error if the server does not support the protocol. If the server does not support the protocol requested by the client or the first message received from the client is not a `HandshakeRequest` message the server must close the connection.
After opening a connection to the server the client must send a `HandshakeRequest` message to the server as its first message. The handshake message is **always** a JSON message and contains the name of the format (protocol) as well as the version of the protocol that will be used for the duration of the connection. The server will reply with a `HandshakeResponse`, also always JSON, containing an error if the server does not support the protocol. If the server does not support the protocol requested by the client or the first message received from the client is not a `HandshakeRequest` message the server must close the connection.
The `HandshakeRequest` message contains the following properties:
* `protocol` - the name of the protocol to be used for messages exchanged between the server and the client
* `version` - the value must always be 1, for both MessagePack and Json protocols
Example:
```json
{
"protocol": "messagepack"
"protocol": "messagepack",
"version": 1
}
```
The `HandshakeResponse` message contains the following properties:
* `error` - the optional error message if the server does not support the request protocol
* `error` - the optional error message if the server does not support the requested protocol
Example:

View File

@ -80,8 +80,8 @@ namespace Microsoft.AspNetCore.SignalR.Client
private static readonly Action<ILogger, string, Exception> _receivedUnexpectedResponse =
LoggerMessage.Define<string>(LogLevel.Error, new EventId(23, "ReceivedUnexpectedResponse"), "Unsolicited response received for invocation '{InvocationId}'.");
private static readonly Action<ILogger, string, Exception> _hubProtocol =
LoggerMessage.Define<string>(LogLevel.Information, new EventId(24, "HubProtocol"), "Using HubProtocol '{Protocol}'.");
private static readonly Action<ILogger, string, int, Exception> _hubProtocol =
LoggerMessage.Define<string, int>(LogLevel.Information, new EventId(24, "HubProtocol"), "Using HubProtocol '{Protocol} v{Version}'.");
private static readonly Action<ILogger, string, string, string, int, Exception> _preparingStreamingInvocation =
LoggerMessage.Define<string, string, string, int>(LogLevel.Trace, new EventId(25, "PreparingStreamingInvocation"), "Preparing streaming invocation '{InvocationId}' of '{Target}', with return type '{ReturnType}' and {ArgumentCount} argument(s).");
@ -253,9 +253,9 @@ namespace Microsoft.AspNetCore.SignalR.Client
_receivedUnexpectedResponse(logger, invocationId, null);
}
public static void HubProtocol(ILogger logger, string hubProtocol)
public static void HubProtocol(ILogger logger, string hubProtocol, int version)
{
_hubProtocol(logger, hubProtocol, null);
_hubProtocol(logger, hubProtocol, version, null);
}
public static void ResettingKeepAliveTimer(ILogger logger)

View File

@ -115,13 +115,13 @@ namespace Microsoft.AspNetCore.SignalR.Client
_needKeepAlive = _connection.Features.Get<IConnectionInherentKeepAliveFeature>() == null;
_receivedHandshakeResponse = false;
Log.HubProtocol(_logger, _protocol.Name);
Log.HubProtocol(_logger, _protocol.Name, _protocol.Version);
_connectionActive = new CancellationTokenSource();
using (var memoryStream = new MemoryStream())
{
Log.SendingHubHandshake(_logger);
HandshakeProtocol.WriteRequestMessage(new HandshakeRequestMessage(_protocol.Name), memoryStream);
HandshakeProtocol.WriteRequestMessage(new HandshakeRequestMessage(_protocol.Name, _protocol.Version), memoryStream);
await _connection.SendAsync(memoryStream.ToArray(), _connectionActive.Token);
}

View File

@ -3,7 +3,6 @@
using System;
using System.Buffers;
using System.Collections;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
@ -17,6 +16,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
private static readonly UTF8Encoding _utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
private const string ProtocolPropertyName = "protocol";
private const string ProtocolVersionName = "version";
private const string ErrorPropertyName = "error";
private const string TypePropertyName = "type";
@ -27,6 +27,8 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
writer.WriteStartObject();
writer.WritePropertyName(ProtocolPropertyName);
writer.WriteValue(requestMessage.Protocol);
writer.WritePropertyName(ProtocolVersionName);
writer.WriteValue(requestMessage.Version);
writer.WriteEndObject();
}
@ -101,7 +103,8 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
var token = JToken.ReadFrom(reader);
var handshakeJObject = JsonUtils.GetObject(token);
var protocol = JsonUtils.GetRequiredProperty<string>(handshakeJObject, ProtocolPropertyName);
requestMessage = new HandshakeRequestMessage(protocol);
var protocolVersion = JsonUtils.GetRequiredProperty<int>(handshakeJObject, ProtocolVersionName, JTokenType.Integer);
requestMessage = new HandshakeRequestMessage(protocol, protocolVersion);
}
return true;

View File

@ -5,11 +5,13 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
{
public class HandshakeRequestMessage : HubMessage
{
public HandshakeRequestMessage(string protocol)
public HandshakeRequestMessage(string protocol, int version)
{
Protocol = protocol;
Version = version;
}
public string Protocol { get; }
public int Version { get; }
}
}

View File

@ -12,10 +12,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
{
string Name { get; }
int Version { get; }
TransferFormat TransferFormat { get; }
bool TryParseMessages(ReadOnlyMemory<byte> input, IInvocationBinder binder, IList<HubMessage> messages);
void WriteMessage(HubMessage message, Stream output);
bool IsVersionSupported(int version);
}
}

View File

@ -29,6 +29,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
private const string HeadersPropertyName = "headers";
public static readonly string ProtocolName = "json";
public static readonly int ProtocolVersion = 1;
// ONLY to be used for application payloads (args, return values, etc.)
public JsonSerializer PayloadSerializer { get; }
@ -44,14 +45,25 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
public string Name => ProtocolName;
public int Version => ProtocolVersion;
public TransferFormat TransferFormat => TransferFormat.Text;
public bool IsVersionSupported(int version)
{
return version == Version;
}
public bool TryParseMessages(ReadOnlyMemory<byte> input, IInvocationBinder binder, IList<HubMessage> messages)
{
while (TextMessageParser.TryParseMessage(ref input, out var payload))
{
var textReader = new Utf8BufferTextReader(payload);
messages.Add(ParseMessage(textReader, binder));
var message = ParseMessage(textReader, binder);
if (message != null)
{
messages.Add(message);
}
}
return messages.Count > 0;
@ -277,7 +289,8 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
case null:
throw new InvalidDataException($"Missing required property '{TypePropertyName}'.");
default:
throw new InvalidDataException($"Unknown message type: {type}");
// Future protocol changes can add message types, old clients can ignore them
return null;
}
return ApplyHeaders(message, headers);

Some files were not shown because too many files have changed in this diff Show More