Skip to content

Commit a2d217c

Browse files
[TS] Add support for negotiateVersion and connectionToken (#14157)
1 parent f3e6b74 commit a2d217c

File tree

4 files changed

+304
-34
lines changed

4 files changed

+304
-34
lines changed

src/SignalR/clients/ts/signalr/src/HttpConnection.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const enum ConnectionState {
2323
/** @private */
2424
export interface INegotiateResponse {
2525
connectionId?: string;
26+
connectionToken?: string;
27+
negotiateVersion?: number;
2628
availableTransports?: IAvailableTransport[];
2729
url?: string;
2830
accessToken?: string;
@@ -70,6 +72,8 @@ export class HttpConnection implements IConnection {
7072
public onreceive: ((data: string | ArrayBuffer) => void) | null;
7173
public onclose: ((e?: Error) => void) | null;
7274

75+
private readonly negotiateVersion: number = 1;
76+
7377
constructor(url: string, options: IHttpConnectionOptions = {}) {
7478
Arg.isRequired(url, "url");
7579

@@ -272,8 +276,6 @@ export class HttpConnection implements IConnection {
272276
throw new Error("Negotiate redirection limit exceeded.");
273277
}
274278

275-
this.connectionId = negotiateResponse.connectionId;
276-
277279
await this.createTransport(url, this.options.transport, negotiateResponse, transferFormat);
278280
}
279281

@@ -322,53 +324,63 @@ export class HttpConnection implements IConnection {
322324
return Promise.reject(new Error(`Unexpected status code returned from negotiate ${response.statusCode}`));
323325
}
324326

325-
return JSON.parse(response.content as string) as INegotiateResponse;
327+
const negotiateResponse = JSON.parse(response.content as string) as INegotiateResponse;
328+
if (!negotiateResponse.negotiateVersion || negotiateResponse.negotiateVersion < 1) {
329+
// Negotiate version 0 doesn't use connectionToken
330+
// So we set it equal to connectionId so all our logic can use connectionToken without being aware of the negotiate version
331+
negotiateResponse.connectionToken = negotiateResponse.connectionId;
332+
}
333+
return negotiateResponse;
326334
} catch (e) {
327335
this.logger.log(LogLevel.Error, "Failed to complete negotiation with the server: " + e);
328336
return Promise.reject(e);
329337
}
330338
}
331339

332-
private createConnectUrl(url: string, connectionId: string | null | undefined) {
333-
if (!connectionId) {
340+
private createConnectUrl(url: string, connectionToken: string | null | undefined) {
341+
if (!connectionToken) {
334342
return url;
335343
}
336-
return url + (url.indexOf("?") === -1 ? "?" : "&") + `id=${connectionId}`;
344+
345+
return url + (url.indexOf("?") === -1 ? "?" : "&") + `id=${connectionToken}`;
337346
}
338347

339348
private async createTransport(url: string, requestedTransport: HttpTransportType | ITransport | undefined, negotiateResponse: INegotiateResponse, requestedTransferFormat: TransferFormat): Promise<void> {
340-
let connectUrl = this.createConnectUrl(url, negotiateResponse.connectionId);
349+
let connectUrl = this.createConnectUrl(url, negotiateResponse.connectionToken);
341350
if (this.isITransport(requestedTransport)) {
342351
this.logger.log(LogLevel.Debug, "Connection was provided an instance of ITransport, using that directly.");
343352
this.transport = requestedTransport;
344353
await this.startTransport(connectUrl, requestedTransferFormat);
345354

355+
this.connectionId = negotiateResponse.connectionId;
346356
return;
347357
}
348358

349359
const transportExceptions: any[] = [];
350360
const transports = negotiateResponse.availableTransports || [];
361+
let negotiate: INegotiateResponse | undefined = negotiateResponse;
351362
for (const endpoint of transports) {
352363
const transportOrError = this.resolveTransportOrError(endpoint, requestedTransport, requestedTransferFormat);
353364
if (transportOrError instanceof Error) {
354365
// Store the error and continue, we don't want to cause a re-negotiate in these cases
355366
transportExceptions.push(`${endpoint.transport} failed: ${transportOrError}`);
356367
} else if (this.isITransport(transportOrError)) {
357368
this.transport = transportOrError;
358-
if (!negotiateResponse.connectionId) {
369+
if (!negotiate) {
359370
try {
360-
negotiateResponse = await this.getNegotiationResponse(url);
371+
negotiate = await this.getNegotiationResponse(url);
361372
} catch (ex) {
362373
return Promise.reject(ex);
363374
}
364-
connectUrl = this.createConnectUrl(url, negotiateResponse.connectionId);
375+
connectUrl = this.createConnectUrl(url, negotiate.connectionToken);
365376
}
366377
try {
367378
await this.startTransport(connectUrl, requestedTransferFormat);
379+
this.connectionId = negotiate.connectionId;
368380
return;
369381
} catch (ex) {
370382
this.logger.log(LogLevel.Error, `Failed to start the transport '${endpoint.transport}': ${ex}`);
371-
negotiateResponse.connectionId = undefined;
383+
negotiate = undefined;
372384
transportExceptions.push(`${endpoint.transport} failed: ${ex}`);
373385

374386
if (this.connectionState !== ConnectionState.Connecting) {
@@ -504,7 +516,7 @@ export class HttpConnection implements IConnection {
504516

505517
// Setting the url to the href propery of an anchor tag handles normalization
506518
// for us. There are 3 main cases.
507-
// 1. Relative path normalization e.g "b" -> "http://localhost:5000/a/b"
519+
// 1. Relative path normalization e.g "b" -> "http://localhost:5000/a/b"
508520
// 2. Absolute path normalization e.g "/a/b" -> "http://localhost:5000/a/b"
509521
// 3. Networkpath reference normalization e.g "//localhost:5000/a/b" -> "http://localhost:5000/a/b"
510522
const aTag = window.document.createElement("a");
@@ -522,6 +534,11 @@ export class HttpConnection implements IConnection {
522534
}
523535
negotiateUrl += "negotiate";
524536
negotiateUrl += index === -1 ? "" : url.substring(index);
537+
538+
if (negotiateUrl.indexOf("negotiateVersion") === -1) {
539+
negotiateUrl += index === -1 ? "?" : "&";
540+
negotiateUrl += "negotiateVersion=" + this.negotiateVersion;
541+
}
525542
return negotiateUrl;
526543
}
527544
}

src/SignalR/clients/ts/signalr/tests/Common.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ export function eachTransport(action: (transport: HttpTransportType) => void) {
1515

1616
export function eachEndpointUrl(action: (givenUrl: string, expectedUrl: string) => void) {
1717
const urls = [
18-
[ "http://tempuri.org/endpoint/?q=my/Data", "http://tempuri.org/endpoint/negotiate?q=my/Data" ],
19-
[ "http://tempuri.org/endpoint?q=my/Data", "http://tempuri.org/endpoint/negotiate?q=my/Data" ],
20-
[ "http://tempuri.org/endpoint", "http://tempuri.org/endpoint/negotiate" ],
21-
[ "http://tempuri.org/endpoint/", "http://tempuri.org/endpoint/negotiate" ],
18+
[ "http://tempuri.org/endpoint/?q=my/Data", "http://tempuri.org/endpoint/negotiate?q=my/Data&negotiateVersion=1" ],
19+
[ "http://tempuri.org/endpoint?q=my/Data", "http://tempuri.org/endpoint/negotiate?q=my/Data&negotiateVersion=1" ],
20+
[ "http://tempuri.org/endpoint", "http://tempuri.org/endpoint/negotiate?negotiateVersion=1" ],
21+
[ "http://tempuri.org/endpoint/", "http://tempuri.org/endpoint/negotiate?negotiateVersion=1" ],
2222
];
2323

2424
urls.forEach((t) => action(t[0], t[1]));

0 commit comments

Comments
 (0)