2021-07-19 01:10:52 +00:00
( function webpackUniversalModuleDefinition ( root , factory ) {
if ( typeof exports === 'object' && typeof module === 'object' )
module . exports = factory ( ) ;
else if ( typeof define === 'function' && define . amd )
define ( [ ] , factory ) ;
else if ( typeof exports === 'object' )
exports [ "signalR" ] = factory ( ) ;
else
root [ "signalR" ] = factory ( ) ;
2024-01-17 03:00:56 +00:00
} ) ( self , ( ) => {
return /******/ ( ( ) => { // webpackBootstrap
/******/ "use strict" ;
/******/ // The require scope
/******/ var _ _webpack _require _ _ = { } ;
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ ( ( ) => {
/******/ // define getter functions for harmony exports
/******/ _ _webpack _require _ _ . d = ( exports , definition ) => {
/******/ for ( var key in definition ) {
/******/ if ( _ _webpack _require _ _ . o ( definition , key ) && ! _ _webpack _require _ _ . o ( exports , key ) ) {
/******/ Object . defineProperty ( exports , key , { enumerable : true , get : definition [ key ] } ) ;
/******/ }
/******/ }
/******/ } ;
/******/ } ) ( ) ;
/******/
/******/ /* webpack/runtime/global */
/******/ ( ( ) => {
/******/ _ _webpack _require _ _ . g = ( function ( ) {
/******/ if ( typeof globalThis === 'object' ) return globalThis ;
/******/ try {
/******/ return this || new Function ( 'return this' ) ( ) ;
/******/ } catch ( e ) {
/******/ if ( typeof window === 'object' ) return window ;
/******/ }
/******/ } ) ( ) ;
/******/ } ) ( ) ;
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ ( ( ) => {
/******/ _ _webpack _require _ _ . o = ( obj , prop ) => ( Object . prototype . hasOwnProperty . call ( obj , prop ) )
/******/ } ) ( ) ;
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ ( ( ) => {
/******/ // define __esModule on exports
/******/ _ _webpack _require _ _ . r = ( exports ) => {
/******/ if ( typeof Symbol !== 'undefined' && Symbol . toStringTag ) {
/******/ Object . defineProperty ( exports , Symbol . toStringTag , { value : 'Module' } ) ;
/******/ }
/******/ Object . defineProperty ( exports , '__esModule' , { value : true } ) ;
2021-07-19 01:10:52 +00:00
/******/ } ;
2024-01-17 03:00:56 +00:00
/******/ } ) ( ) ;
/******/
2021-07-19 01:10:52 +00:00
/************************************************************************/
2024-01-17 03:00:56 +00:00
var _ _webpack _exports _ _ = { } ;
// ESM COMPAT FLAG
2021-07-19 01:10:52 +00:00
_ _webpack _require _ _ . r ( _ _webpack _exports _ _ ) ;
2024-01-17 03:00:56 +00:00
// EXPORTS
_ _webpack _require _ _ . d ( _ _webpack _exports _ _ , {
"AbortError" : ( ) => ( /* reexport */ AbortError ) ,
"DefaultHttpClient" : ( ) => ( /* reexport */ DefaultHttpClient ) ,
"HttpClient" : ( ) => ( /* reexport */ HttpClient ) ,
"HttpError" : ( ) => ( /* reexport */ HttpError ) ,
"HttpResponse" : ( ) => ( /* reexport */ HttpResponse ) ,
"HttpTransportType" : ( ) => ( /* reexport */ HttpTransportType ) ,
"HubConnection" : ( ) => ( /* reexport */ HubConnection ) ,
"HubConnectionBuilder" : ( ) => ( /* reexport */ HubConnectionBuilder ) ,
"HubConnectionState" : ( ) => ( /* reexport */ HubConnectionState ) ,
"JsonHubProtocol" : ( ) => ( /* reexport */ JsonHubProtocol ) ,
"LogLevel" : ( ) => ( /* reexport */ LogLevel ) ,
"MessageType" : ( ) => ( /* reexport */ MessageType ) ,
"NullLogger" : ( ) => ( /* reexport */ NullLogger ) ,
"Subject" : ( ) => ( /* reexport */ Subject ) ,
"TimeoutError" : ( ) => ( /* reexport */ TimeoutError ) ,
"TransferFormat" : ( ) => ( /* reexport */ TransferFormat ) ,
"VERSION" : ( ) => ( /* reexport */ VERSION )
} ) ;
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/Errors.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
/** Error thrown when an HTTP request fails. */
2024-01-17 03:00:56 +00:00
class HttpError extends Error {
2021-07-19 01:10:52 +00:00
/ * * C o n s t r u c t s a n e w i n s t a n c e o f { @ l i n k @ m i c r o s o f t / s i g n a l r . H t t p E r r o r } .
*
* @ param { string } errorMessage A descriptive error message .
* @ param { number } statusCode The HTTP status code represented by this error .
* /
2024-01-17 03:00:56 +00:00
constructor ( errorMessage , statusCode ) {
const trueProto = new . target . prototype ;
super ( ` ${ errorMessage } : Status code ' ${ statusCode } ' ` ) ;
this . statusCode = statusCode ;
2021-07-19 01:10:52 +00:00
// Workaround issue in Typescript compiler
// https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200
2024-01-17 03:00:56 +00:00
this . _ _proto _ _ = trueProto ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
2021-07-19 01:10:52 +00:00
/** Error thrown when a timeout elapses. */
2024-01-17 03:00:56 +00:00
class TimeoutError extends Error {
2021-07-19 01:10:52 +00:00
/ * * C o n s t r u c t s a n e w i n s t a n c e o f { @ l i n k @ m i c r o s o f t / s i g n a l r . T i m e o u t E r r o r } .
*
* @ param { string } errorMessage A descriptive error message .
* /
2024-01-17 03:00:56 +00:00
constructor ( errorMessage = "A timeout occurred." ) {
const trueProto = new . target . prototype ;
super ( errorMessage ) ;
2021-07-19 01:10:52 +00:00
// Workaround issue in Typescript compiler
// https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200
2024-01-17 03:00:56 +00:00
this . _ _proto _ _ = trueProto ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
2021-07-19 01:10:52 +00:00
/** Error thrown when an action is aborted. */
2024-01-17 03:00:56 +00:00
class AbortError extends Error {
2021-07-19 01:10:52 +00:00
/ * * C o n s t r u c t s a n e w i n s t a n c e o f { @ l i n k A b o r t E r r o r } .
*
* @ param { string } errorMessage A descriptive error message .
* /
2024-01-17 03:00:56 +00:00
constructor ( errorMessage = "An abort occurred." ) {
const trueProto = new . target . prototype ;
super ( errorMessage ) ;
2021-07-19 01:10:52 +00:00
// Workaround issue in Typescript compiler
// https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200
2024-01-17 03:00:56 +00:00
this . _ _proto _ _ = trueProto ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
/** Error thrown when the selected transport is unsupported by the browser. */
/** @private */
class UnsupportedTransportError extends Error {
/ * * C o n s t r u c t s a n e w i n s t a n c e o f { @ l i n k @ m i c r o s o f t / s i g n a l r . U n s u p p o r t e d T r a n s p o r t E r r o r } .
*
* @ param { string } message A descriptive error message .
* @ param { HttpTransportType } transport The { @ link @ microsoft / signalr . HttpTransportType } this error occurred on .
* /
constructor ( message , transport ) {
const trueProto = new . target . prototype ;
super ( message ) ;
this . transport = transport ;
this . errorType = 'UnsupportedTransportError' ;
// Workaround issue in Typescript compiler
// https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200
this . _ _proto _ _ = trueProto ;
}
}
/** Error thrown when the selected transport is disabled by the browser. */
/** @private */
class DisabledTransportError extends Error {
/ * * C o n s t r u c t s a n e w i n s t a n c e o f { @ l i n k @ m i c r o s o f t / s i g n a l r . D i s a b l e d T r a n s p o r t E r r o r } .
*
* @ param { string } message A descriptive error message .
* @ param { HttpTransportType } transport The { @ link @ microsoft / signalr . HttpTransportType } this error occurred on .
* /
constructor ( message , transport ) {
const trueProto = new . target . prototype ;
super ( message ) ;
this . transport = transport ;
this . errorType = 'DisabledTransportError' ;
// Workaround issue in Typescript compiler
// https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200
this . _ _proto _ _ = trueProto ;
}
}
/** Error thrown when the selected transport cannot be started. */
/** @private */
class FailedToStartTransportError extends Error {
/ * * C o n s t r u c t s a n e w i n s t a n c e o f { @ l i n k @ m i c r o s o f t / s i g n a l r . F a i l e d T o S t a r t T r a n s p o r t E r r o r } .
*
* @ param { string } message A descriptive error message .
* @ param { HttpTransportType } transport The { @ link @ microsoft / signalr . HttpTransportType } this error occurred on .
* /
constructor ( message , transport ) {
const trueProto = new . target . prototype ;
super ( message ) ;
this . transport = transport ;
this . errorType = 'FailedToStartTransportError' ;
// Workaround issue in Typescript compiler
// https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200
this . _ _proto _ _ = trueProto ;
}
}
/** Error thrown when the negotiation with the server failed to complete. */
/** @private */
class FailedToNegotiateWithServerError extends Error {
/ * * C o n s t r u c t s a n e w i n s t a n c e o f { @ l i n k @ m i c r o s o f t / s i g n a l r . F a i l e d T o N e g o t i a t e W i t h S e r v e r E r r o r } .
*
* @ param { string } message A descriptive error message .
* /
constructor ( message ) {
const trueProto = new . target . prototype ;
super ( message ) ;
this . errorType = 'FailedToNegotiateWithServerError' ;
// Workaround issue in Typescript compiler
// https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200
this . _ _proto _ _ = trueProto ;
}
}
/** Error thrown when multiple errors have occurred. */
/** @private */
class AggregateErrors extends Error {
/ * * C o n s t r u c t s a n e w i n s t a n c e o f { @ l i n k @ m i c r o s o f t / s i g n a l r . A g g r e g a t e E r r o r s } .
*
* @ param { string } message A descriptive error message .
* @ param { Error [ ] } innerErrors The collection of errors this error is aggregating .
* /
constructor ( message , innerErrors ) {
const trueProto = new . target . prototype ;
super ( message ) ;
this . innerErrors = innerErrors ;
// Workaround issue in Typescript compiler
// https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200
this . _ _proto _ _ = trueProto ;
}
}
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/HttpClient.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
/** Represents an HTTP response. */
2024-01-17 03:00:56 +00:00
class HttpResponse {
constructor ( statusCode , statusText , content ) {
2021-07-19 01:10:52 +00:00
this . statusCode = statusCode ;
this . statusText = statusText ;
this . content = content ;
}
2024-01-17 03:00:56 +00:00
}
2021-07-19 01:10:52 +00:00
/ * * A b s t r a c t i o n o v e r a n H T T P c l i e n t .
*
* This class provides an abstraction over an HTTP client so that a different implementation can be provided on different platforms .
* /
2024-01-17 03:00:56 +00:00
class HttpClient {
get ( url , options ) {
return this . send ( {
... options ,
method : "GET" ,
url ,
} ) ;
}
post ( url , options ) {
return this . send ( {
... options ,
method : "POST" ,
url ,
} ) ;
}
delete ( url , options ) {
return this . send ( {
... options ,
method : "DELETE" ,
url ,
} ) ;
}
2021-07-19 01:10:52 +00:00
/ * * G e t s a l l c o o k i e s t h a t a p p l y t o t h e s p e c i f i e d U R L .
*
* @ param url The URL that the cookies are valid for .
* @ returns { string } A string containing all the key - value cookie pairs for the specified URL .
* /
// @ts-ignore
2024-01-17 03:00:56 +00:00
getCookieString ( url ) {
2021-07-19 01:10:52 +00:00
return "" ;
}
}
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/ILogger.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
// These values are designed to match the ASP.NET Log Levels since that's the pattern we're emulating here.
/ * * I n d i c a t e s t h e s e v e r i t y o f a l o g m e s s a g e .
*
* Log Levels are ordered in increasing severity . So ` Debug ` is more severe than ` Trace ` , etc .
* /
var LogLevel ;
( function ( LogLevel ) {
/** Log level for very low severity diagnostic messages. */
LogLevel [ LogLevel [ "Trace" ] = 0 ] = "Trace" ;
/** Log level for low severity diagnostic messages. */
LogLevel [ LogLevel [ "Debug" ] = 1 ] = "Debug" ;
/** Log level for informational diagnostic messages. */
LogLevel [ LogLevel [ "Information" ] = 2 ] = "Information" ;
/** Log level for diagnostic messages that indicate a non-fatal problem. */
LogLevel [ LogLevel [ "Warning" ] = 3 ] = "Warning" ;
/** Log level for diagnostic messages that indicate a failure in the current operation. */
LogLevel [ LogLevel [ "Error" ] = 4 ] = "Error" ;
/** Log level for diagnostic messages that indicate a failure that will terminate the entire application. */
LogLevel [ LogLevel [ "Critical" ] = 5 ] = "Critical" ;
/** The highest possible log level. Used when configuring logging to indicate that no log messages should be emitted. */
LogLevel [ LogLevel [ "None" ] = 6 ] = "None" ;
} ) ( LogLevel || ( LogLevel = { } ) ) ;
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/Loggers.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/** A logger that does nothing when log messages are sent to it. */
class NullLogger {
constructor ( ) { }
/** @inheritDoc */
// eslint-disable-next-line
log ( _logLevel , _message ) {
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
/** The singleton instance of the {@link @microsoft/signalr.NullLogger}. */
NullLogger . instance = new NullLogger ( ) ;
; // CONCATENATED MODULE: ./src/Utils.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
// Version token that will be replaced by the prepack command
/** The version of the SignalR client. */
2024-01-17 03:00:56 +00:00
const VERSION = "8.0.0" ;
2021-07-19 01:10:52 +00:00
/** @private */
2024-01-17 03:00:56 +00:00
class Arg {
static isRequired ( val , name ) {
2021-07-19 01:10:52 +00:00
if ( val === null || val === undefined ) {
2024-01-17 03:00:56 +00:00
throw new Error ( ` The ' ${ name } ' argument is required. ` ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
static isNotEmpty ( val , name ) {
2021-07-19 01:10:52 +00:00
if ( ! val || val . match ( /^\s*$/ ) ) {
2024-01-17 03:00:56 +00:00
throw new Error ( ` The ' ${ name } ' argument should not be empty. ` ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
static isIn ( val , values , name ) {
2021-07-19 01:10:52 +00:00
// TypeScript enums have keys for **both** the name and the value of each enum member on the type itself.
if ( ! ( val in values ) ) {
2024-01-17 03:00:56 +00:00
throw new Error ( ` Unknown ${ name } value: ${ val } . ` ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
}
2021-07-19 01:10:52 +00:00
/** @private */
2024-01-17 03:00:56 +00:00
class Platform {
// react-native has a window but no document so we should check both
static get isBrowser ( ) {
return ! Platform . isNode && typeof window === "object" && typeof window . document === "object" ;
}
// WebWorkers don't have a window object so the isBrowser check would fail
static get isWebWorker ( ) {
return ! Platform . isNode && typeof self === "object" && "importScripts" in self ;
}
// react-native has a window but no document
static get isReactNative ( ) {
return ! Platform . isNode && typeof window === "object" && typeof window . document === "undefined" ;
}
// Node apps shouldn't have a window object, but WebWorkers don't either
// so we need to check for both WebWorker and window
static get isNode ( ) {
return typeof process !== "undefined" && process . release && process . release . name === "node" ;
}
}
2021-07-19 01:10:52 +00:00
/** @private */
function getDataDetail ( data , includeContent ) {
2024-01-17 03:00:56 +00:00
let detail = "" ;
2021-07-19 01:10:52 +00:00
if ( isArrayBuffer ( data ) ) {
2024-01-17 03:00:56 +00:00
detail = ` Binary data of length ${ data . byteLength } ` ;
2021-07-19 01:10:52 +00:00
if ( includeContent ) {
2024-01-17 03:00:56 +00:00
detail += ` . Content: ' ${ formatArrayBuffer ( data ) } ' ` ;
2021-07-19 01:10:52 +00:00
}
}
else if ( typeof data === "string" ) {
2024-01-17 03:00:56 +00:00
detail = ` String data of length ${ data . length } ` ;
2021-07-19 01:10:52 +00:00
if ( includeContent ) {
2024-01-17 03:00:56 +00:00
detail += ` . Content: ' ${ data } ' ` ;
2021-07-19 01:10:52 +00:00
}
}
return detail ;
}
/** @private */
function formatArrayBuffer ( data ) {
2024-01-17 03:00:56 +00:00
const view = new Uint8Array ( data ) ;
2021-07-19 01:10:52 +00:00
// Uint8Array.map only supports returning another Uint8Array?
2024-01-17 03:00:56 +00:00
let str = "" ;
view . forEach ( ( num ) => {
const pad = num < 16 ? "0" : "" ;
str += ` 0x ${ pad } ${ num . toString ( 16 ) } ` ;
2021-07-19 01:10:52 +00:00
} ) ;
// Trim of trailing space.
return str . substr ( 0 , str . length - 1 ) ;
}
// Also in signalr-protocol-msgpack/Utils.ts
/** @private */
function isArrayBuffer ( val ) {
return val && typeof ArrayBuffer !== "undefined" &&
( val instanceof ArrayBuffer ||
// Sometimes we get an ArrayBuffer that doesn't satisfy instanceof
( val . constructor && val . constructor . name === "ArrayBuffer" ) ) ;
}
/** @private */
2024-01-17 03:00:56 +00:00
async function sendMessage ( logger , transportName , httpClient , url , content , options ) {
const headers = { } ;
const [ name , value ] = getUserAgentHeader ( ) ;
headers [ name ] = value ;
logger . log ( LogLevel . Trace , ` ( ${ transportName } transport) sending data. ${ getDataDetail ( content , options . logMessageContent ) } . ` ) ;
const responseType = isArrayBuffer ( content ) ? "arraybuffer" : "text" ;
const response = await httpClient . post ( url , {
content ,
headers : { ... headers , ... options . headers } ,
responseType ,
timeout : options . timeout ,
withCredentials : options . withCredentials ,
2021-07-19 01:10:52 +00:00
} ) ;
2024-01-17 03:00:56 +00:00
logger . log ( LogLevel . Trace , ` ( ${ transportName } transport) request complete. Response status: ${ response . statusCode } . ` ) ;
2021-07-19 01:10:52 +00:00
}
/** @private */
function createLogger ( logger ) {
if ( logger === undefined ) {
2024-01-17 03:00:56 +00:00
return new ConsoleLogger ( LogLevel . Information ) ;
2021-07-19 01:10:52 +00:00
}
if ( logger === null ) {
2024-01-17 03:00:56 +00:00
return NullLogger . instance ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
if ( logger . log !== undefined ) {
2021-07-19 01:10:52 +00:00
return logger ;
}
return new ConsoleLogger ( logger ) ;
}
/** @private */
2024-01-17 03:00:56 +00:00
class SubjectSubscription {
constructor ( subject , observer ) {
this . _subject = subject ;
this . _observer = observer ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
dispose ( ) {
const index = this . _subject . observers . indexOf ( this . _observer ) ;
2021-07-19 01:10:52 +00:00
if ( index > - 1 ) {
2024-01-17 03:00:56 +00:00
this . _subject . observers . splice ( index , 1 ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
if ( this . _subject . observers . length === 0 && this . _subject . cancelCallback ) {
this . _subject . cancelCallback ( ) . catch ( ( _ ) => { } ) ;
2021-07-19 01:10:52 +00:00
}
}
2024-01-17 03:00:56 +00:00
}
/** @private */
class ConsoleLogger {
constructor ( minimumLogLevel ) {
this . _minLevel = minimumLogLevel ;
this . out = console ;
}
log ( logLevel , message ) {
if ( logLevel >= this . _minLevel ) {
const msg = ` [ ${ new Date ( ) . toISOString ( ) } ] ${ LogLevel [ logLevel ] } : ${ message } ` ;
2021-07-19 01:10:52 +00:00
switch ( logLevel ) {
2024-01-17 03:00:56 +00:00
case LogLevel . Critical :
case LogLevel . Error :
this . out . error ( msg ) ;
2021-07-19 01:10:52 +00:00
break ;
2024-01-17 03:00:56 +00:00
case LogLevel . Warning :
this . out . warn ( msg ) ;
2021-07-19 01:10:52 +00:00
break ;
2024-01-17 03:00:56 +00:00
case LogLevel . Information :
this . out . info ( msg ) ;
2021-07-19 01:10:52 +00:00
break ;
default :
// console.debug only goes to attached debuggers in Node, so we use console.log for Trace and Debug
2024-01-17 03:00:56 +00:00
this . out . log ( msg ) ;
2021-07-19 01:10:52 +00:00
break ;
}
}
2024-01-17 03:00:56 +00:00
}
}
2021-07-19 01:10:52 +00:00
/** @private */
function getUserAgentHeader ( ) {
2024-01-17 03:00:56 +00:00
let userAgentHeaderName = "X-SignalR-User-Agent" ;
2021-07-19 01:10:52 +00:00
if ( Platform . isNode ) {
userAgentHeaderName = "User-Agent" ;
}
return [ userAgentHeaderName , constructUserAgent ( VERSION , getOsName ( ) , getRuntime ( ) , getRuntimeVersion ( ) ) ] ;
}
/** @private */
function constructUserAgent ( version , os , runtime , runtimeVersion ) {
// Microsoft SignalR/[Version] ([Detailed Version]; [Operating System]; [Runtime]; [Runtime Version])
2024-01-17 03:00:56 +00:00
let userAgent = "Microsoft SignalR/" ;
const majorAndMinor = version . split ( "." ) ;
userAgent += ` ${ majorAndMinor [ 0 ] } . ${ majorAndMinor [ 1 ] } ` ;
userAgent += ` ( ${ version } ; ` ;
2021-07-19 01:10:52 +00:00
if ( os && os !== "" ) {
2024-01-17 03:00:56 +00:00
userAgent += ` ${ os } ; ` ;
2021-07-19 01:10:52 +00:00
}
else {
userAgent += "Unknown OS; " ;
}
2024-01-17 03:00:56 +00:00
userAgent += ` ${ runtime } ` ;
2021-07-19 01:10:52 +00:00
if ( runtimeVersion ) {
2024-01-17 03:00:56 +00:00
userAgent += ` ; ${ runtimeVersion } ` ;
2021-07-19 01:10:52 +00:00
}
else {
userAgent += "; Unknown Runtime Version" ;
}
userAgent += ")" ;
return userAgent ;
}
2024-01-17 03:00:56 +00:00
// eslint-disable-next-line spaced-comment
/*#__PURE__*/ function getOsName ( ) {
2021-07-19 01:10:52 +00:00
if ( Platform . isNode ) {
switch ( process . platform ) {
case "win32" :
return "Windows NT" ;
case "darwin" :
return "macOS" ;
case "linux" :
return "Linux" ;
default :
return process . platform ;
}
}
else {
return "" ;
}
}
2024-01-17 03:00:56 +00:00
// eslint-disable-next-line spaced-comment
/*#__PURE__*/ function getRuntimeVersion ( ) {
2021-07-19 01:10:52 +00:00
if ( Platform . isNode ) {
return process . versions . node ;
}
return undefined ;
}
function getRuntime ( ) {
if ( Platform . isNode ) {
return "NodeJS" ;
}
else {
return "Browser" ;
}
}
2024-01-17 03:00:56 +00:00
/** @private */
function getErrorString ( e ) {
if ( e . stack ) {
return e . stack ;
}
else if ( e . message ) {
return e . message ;
}
return ` ${ e } ` ;
}
/** @private */
function getGlobalThis ( ) {
// globalThis is semi-new and not available in Node until v12
if ( typeof globalThis !== "undefined" ) {
return globalThis ;
}
if ( typeof self !== "undefined" ) {
return self ;
}
if ( typeof window !== "undefined" ) {
return window ;
}
if ( typeof _ _webpack _require _ _ . g !== "undefined" ) {
return _ _webpack _require _ _ . g ;
}
throw new Error ( "could not find global" ) ;
}
; // CONCATENATED MODULE: ./src/DynamicImports.browser.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/** @private */
function configureFetch ( ) {
return false ;
}
/** @private */
function configureAbortController ( ) {
return false ;
}
/** @private */
function getWS ( ) {
throw new Error ( "Trying to import 'ws' in the browser." ) ;
}
/** @private */
function getEventSource ( ) {
throw new Error ( "Trying to import 'eventsource' in the browser." ) ;
}
; // CONCATENATED MODULE: ./src/FetchHttpClient.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
class FetchHttpClient extends HttpClient {
constructor ( logger ) {
super ( ) ;
this . _logger = logger ;
// This is how you do "reference" arguments
const fetchObj = { _fetchType : undefined , _jar : undefined } ;
if ( configureFetch ( fetchObj ) ) {
this . _fetchType = fetchObj . _fetchType ;
this . _jar = fetchObj . _jar ;
}
else {
this . _fetchType = fetch . bind ( getGlobalThis ( ) ) ;
}
this . _abortControllerType = AbortController ;
const abortObj = { _abortControllerType : this . _abortControllerType } ;
if ( configureAbortController ( abortObj ) ) {
this . _abortControllerType = abortObj . _abortControllerType ;
}
}
/** @inheritDoc */
async send ( request ) {
// Check that abort was not signaled before calling send
if ( request . abortSignal && request . abortSignal . aborted ) {
throw new AbortError ( ) ;
}
if ( ! request . method ) {
throw new Error ( "No method defined." ) ;
}
if ( ! request . url ) {
throw new Error ( "No url defined." ) ;
}
const abortController = new this . _abortControllerType ( ) ;
let error ;
// Hook our abortSignal into the abort controller
if ( request . abortSignal ) {
request . abortSignal . onabort = ( ) => {
abortController . abort ( ) ;
error = new AbortError ( ) ;
} ;
}
// If a timeout has been passed in, setup a timeout to call abort
// Type needs to be any to fit window.setTimeout and NodeJS.setTimeout
let timeoutId = null ;
if ( request . timeout ) {
const msTimeout = request . timeout ;
timeoutId = setTimeout ( ( ) => {
abortController . abort ( ) ;
this . _logger . log ( LogLevel . Warning , ` Timeout from HTTP request. ` ) ;
error = new TimeoutError ( ) ;
} , msTimeout ) ;
}
if ( request . content === "" ) {
request . content = undefined ;
}
if ( request . content ) {
// Explicitly setting the Content-Type header for React Native on Android platform.
request . headers = request . headers || { } ;
if ( isArrayBuffer ( request . content ) ) {
request . headers [ "Content-Type" ] = "application/octet-stream" ;
}
else {
request . headers [ "Content-Type" ] = "text/plain;charset=UTF-8" ;
}
}
let response ;
try {
response = await this . _fetchType ( request . url , {
body : request . content ,
cache : "no-cache" ,
credentials : request . withCredentials === true ? "include" : "same-origin" ,
headers : {
"X-Requested-With" : "XMLHttpRequest" ,
... request . headers ,
} ,
method : request . method ,
mode : "cors" ,
redirect : "follow" ,
signal : abortController . signal ,
} ) ;
}
catch ( e ) {
if ( error ) {
throw error ;
}
this . _logger . log ( LogLevel . Warning , ` Error from HTTP request. ${ e } . ` ) ;
throw e ;
}
finally {
if ( timeoutId ) {
clearTimeout ( timeoutId ) ;
}
if ( request . abortSignal ) {
request . abortSignal . onabort = null ;
}
}
if ( ! response . ok ) {
const errorMessage = await deserializeContent ( response , "text" ) ;
throw new HttpError ( errorMessage || response . statusText , response . status ) ;
}
const content = deserializeContent ( response , request . responseType ) ;
const payload = await content ;
return new HttpResponse ( response . status , response . statusText , payload ) ;
}
getCookieString ( url ) {
let cookies = "" ;
if ( Platform . isNode && this . _jar ) {
// @ts-ignore: unused variable
this . _jar . getCookies ( url , ( e , c ) => cookies = c . join ( "; " ) ) ;
}
return cookies ;
}
}
function deserializeContent ( response , responseType ) {
let content ;
switch ( responseType ) {
case "arraybuffer" :
content = response . arrayBuffer ( ) ;
break ;
case "text" :
content = response . text ( ) ;
break ;
case "blob" :
case "document" :
case "json" :
throw new Error ( ` ${ responseType } is not supported. ` ) ;
default :
content = response . text ( ) ;
break ;
}
return content ;
}
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/XhrHttpClient.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
class XhrHttpClient extends HttpClient {
constructor ( logger ) {
super ( ) ;
this . _logger = logger ;
2021-07-19 01:10:52 +00:00
}
/** @inheritDoc */
2024-01-17 03:00:56 +00:00
send ( request ) {
2021-07-19 01:10:52 +00:00
// Check that abort was not signaled before calling send
if ( request . abortSignal && request . abortSignal . aborted ) {
2024-01-17 03:00:56 +00:00
return Promise . reject ( new AbortError ( ) ) ;
2021-07-19 01:10:52 +00:00
}
if ( ! request . method ) {
return Promise . reject ( new Error ( "No method defined." ) ) ;
}
if ( ! request . url ) {
return Promise . reject ( new Error ( "No url defined." ) ) ;
}
2024-01-17 03:00:56 +00:00
return new Promise ( ( resolve , reject ) => {
const xhr = new XMLHttpRequest ( ) ;
2021-07-19 01:10:52 +00:00
xhr . open ( request . method , request . url , true ) ;
xhr . withCredentials = request . withCredentials === undefined ? true : request . withCredentials ;
xhr . setRequestHeader ( "X-Requested-With" , "XMLHttpRequest" ) ;
2024-01-17 03:00:56 +00:00
if ( request . content === "" ) {
request . content = undefined ;
}
if ( request . content ) {
// Explicitly setting the Content-Type header for React Native on Android platform.
if ( isArrayBuffer ( request . content ) ) {
xhr . setRequestHeader ( "Content-Type" , "application/octet-stream" ) ;
}
else {
xhr . setRequestHeader ( "Content-Type" , "text/plain;charset=UTF-8" ) ;
}
}
const headers = request . headers ;
2021-07-19 01:10:52 +00:00
if ( headers ) {
Object . keys ( headers )
2024-01-17 03:00:56 +00:00
. forEach ( ( header ) => {
2021-07-19 01:10:52 +00:00
xhr . setRequestHeader ( header , headers [ header ] ) ;
} ) ;
}
if ( request . responseType ) {
xhr . responseType = request . responseType ;
}
if ( request . abortSignal ) {
2024-01-17 03:00:56 +00:00
request . abortSignal . onabort = ( ) => {
2021-07-19 01:10:52 +00:00
xhr . abort ( ) ;
2024-01-17 03:00:56 +00:00
reject ( new AbortError ( ) ) ;
2021-07-19 01:10:52 +00:00
} ;
}
if ( request . timeout ) {
xhr . timeout = request . timeout ;
}
2024-01-17 03:00:56 +00:00
xhr . onload = ( ) => {
2021-07-19 01:10:52 +00:00
if ( request . abortSignal ) {
request . abortSignal . onabort = null ;
}
if ( xhr . status >= 200 && xhr . status < 300 ) {
2024-01-17 03:00:56 +00:00
resolve ( new HttpResponse ( xhr . status , xhr . statusText , xhr . response || xhr . responseText ) ) ;
2021-07-19 01:10:52 +00:00
}
else {
2024-01-17 03:00:56 +00:00
reject ( new HttpError ( xhr . response || xhr . responseText || xhr . statusText , xhr . status ) ) ;
2021-07-19 01:10:52 +00:00
}
} ;
2024-01-17 03:00:56 +00:00
xhr . onerror = ( ) => {
this . _logger . log ( LogLevel . Warning , ` Error from HTTP request. ${ xhr . status } : ${ xhr . statusText } . ` ) ;
reject ( new HttpError ( xhr . statusText , xhr . status ) ) ;
2021-07-19 01:10:52 +00:00
} ;
2024-01-17 03:00:56 +00:00
xhr . ontimeout = ( ) => {
this . _logger . log ( LogLevel . Warning , ` Timeout from HTTP request. ` ) ;
reject ( new TimeoutError ( ) ) ;
2021-07-19 01:10:52 +00:00
} ;
2024-01-17 03:00:56 +00:00
xhr . send ( request . content ) ;
2021-07-19 01:10:52 +00:00
} ) ;
2024-01-17 03:00:56 +00:00
}
}
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/DefaultHttpClient.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
/** Default implementation of {@link @microsoft/signalr.HttpClient}. */
class DefaultHttpClient extends HttpClient {
/** Creates a new instance of the {@link @microsoft/signalr.DefaultHttpClient}, using the provided {@link @microsoft/signalr.ILogger} to log messages. */
constructor ( logger ) {
super ( ) ;
if ( typeof fetch !== "undefined" || Platform . isNode ) {
this . _httpClient = new FetchHttpClient ( logger ) ;
}
else if ( typeof XMLHttpRequest !== "undefined" ) {
this . _httpClient = new XhrHttpClient ( logger ) ;
}
else {
throw new Error ( "No usable HttpClient found." ) ;
}
}
/** @inheritDoc */
send ( request ) {
// Check that abort was not signaled before calling send
if ( request . abortSignal && request . abortSignal . aborted ) {
return Promise . reject ( new AbortError ( ) ) ;
}
if ( ! request . method ) {
return Promise . reject ( new Error ( "No method defined." ) ) ;
}
if ( ! request . url ) {
return Promise . reject ( new Error ( "No url defined." ) ) ;
}
return this . _httpClient . send ( request ) ;
}
getCookieString ( url ) {
return this . _httpClient . getCookieString ( url ) ;
}
}
; // CONCATENATED MODULE: ./src/TextMessageFormat.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// Not exported from index
/** @private */
class TextMessageFormat {
static write ( output ) {
return ` ${ output } ${ TextMessageFormat . RecordSeparator } ` ;
}
static parse ( input ) {
if ( input [ input . length - 1 ] !== TextMessageFormat . RecordSeparator ) {
throw new Error ( "Message is incomplete." ) ;
}
const messages = input . split ( TextMessageFormat . RecordSeparator ) ;
messages . pop ( ) ;
return messages ;
}
}
TextMessageFormat . RecordSeparatorCode = 0x1e ;
TextMessageFormat . RecordSeparator = String . fromCharCode ( TextMessageFormat . RecordSeparatorCode ) ;
; // CONCATENATED MODULE: ./src/HandshakeProtocol.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/** @private */
class HandshakeProtocol {
// Handshake request is always JSON
writeHandshakeRequest ( handshakeRequest ) {
return TextMessageFormat . write ( JSON . stringify ( handshakeRequest ) ) ;
}
parseHandshakeResponse ( data ) {
let messageData ;
let remainingData ;
if ( isArrayBuffer ( data ) ) {
// Format is binary but still need to read JSON text from handshake response
const binaryData = new Uint8Array ( data ) ;
const separatorIndex = binaryData . indexOf ( TextMessageFormat . RecordSeparatorCode ) ;
if ( separatorIndex === - 1 ) {
throw new Error ( "Message is incomplete." ) ;
}
// content before separator is handshake response
// optional content after is additional messages
const responseLength = separatorIndex + 1 ;
messageData = String . fromCharCode . apply ( null , Array . prototype . slice . call ( binaryData . slice ( 0 , responseLength ) ) ) ;
remainingData = ( binaryData . byteLength > responseLength ) ? binaryData . slice ( responseLength ) . buffer : null ;
}
else {
const textData = data ;
const separatorIndex = textData . indexOf ( TextMessageFormat . RecordSeparator ) ;
if ( separatorIndex === - 1 ) {
throw new Error ( "Message is incomplete." ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
// content before separator is handshake response
// optional content after is additional messages
const responseLength = separatorIndex + 1 ;
messageData = textData . substring ( 0 , responseLength ) ;
remainingData = ( textData . length > responseLength ) ? textData . substring ( responseLength ) : null ;
}
// At this point we should have just the single handshake message
const messages = TextMessageFormat . parse ( messageData ) ;
const response = JSON . parse ( messages [ 0 ] ) ;
if ( response . type ) {
throw new Error ( "Expected a handshake response from the server." ) ;
}
const responseMessage = response ;
// multiple messages could have arrived with handshake
// return additional data to be parsed as usual, or null if all parsed
return [ remainingData , responseMessage ] ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
; // CONCATENATED MODULE: ./src/IHubProtocol.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/** Defines the type of a Hub Message. */
var MessageType ;
( function ( MessageType ) {
/** Indicates the message is an Invocation message and implements the {@link @microsoft/signalr.InvocationMessage} interface. */
MessageType [ MessageType [ "Invocation" ] = 1 ] = "Invocation" ;
/** Indicates the message is a StreamItem message and implements the {@link @microsoft/signalr.StreamItemMessage} interface. */
MessageType [ MessageType [ "StreamItem" ] = 2 ] = "StreamItem" ;
/** Indicates the message is a Completion message and implements the {@link @microsoft/signalr.CompletionMessage} interface. */
MessageType [ MessageType [ "Completion" ] = 3 ] = "Completion" ;
/** Indicates the message is a Stream Invocation message and implements the {@link @microsoft/signalr.StreamInvocationMessage} interface. */
MessageType [ MessageType [ "StreamInvocation" ] = 4 ] = "StreamInvocation" ;
/** Indicates the message is a Cancel Invocation message and implements the {@link @microsoft/signalr.CancelInvocationMessage} interface. */
MessageType [ MessageType [ "CancelInvocation" ] = 5 ] = "CancelInvocation" ;
/** Indicates the message is a Ping message and implements the {@link @microsoft/signalr.PingMessage} interface. */
MessageType [ MessageType [ "Ping" ] = 6 ] = "Ping" ;
/** Indicates the message is a Close message and implements the {@link @microsoft/signalr.CloseMessage} interface. */
MessageType [ MessageType [ "Close" ] = 7 ] = "Close" ;
MessageType [ MessageType [ "Ack" ] = 8 ] = "Ack" ;
MessageType [ MessageType [ "Sequence" ] = 9 ] = "Sequence" ;
} ) ( MessageType || ( MessageType = { } ) ) ;
; // CONCATENATED MODULE: ./src/Subject.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/** Stream implementation to stream items to the server. */
class Subject {
constructor ( ) {
this . observers = [ ] ;
}
next ( item ) {
for ( const observer of this . observers ) {
observer . next ( item ) ;
}
}
error ( err ) {
for ( const observer of this . observers ) {
if ( observer . error ) {
observer . error ( err ) ;
}
}
}
complete ( ) {
for ( const observer of this . observers ) {
if ( observer . complete ) {
observer . complete ( ) ;
}
}
}
subscribe ( observer ) {
this . observers . push ( observer ) ;
return new SubjectSubscription ( this , observer ) ;
}
}
; // CONCATENATED MODULE: ./src/MessageBuffer.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/** @private */
class MessageBuffer {
constructor ( protocol , connection , bufferSize ) {
this . _bufferSize = 100000 ;
this . _messages = [ ] ;
this . _totalMessageCount = 0 ;
this . _waitForSequenceMessage = false ;
// Message IDs start at 1 and always increment by 1
this . _nextReceivingSequenceId = 1 ;
this . _latestReceivedSequenceId = 0 ;
this . _bufferedByteCount = 0 ;
this . _reconnectInProgress = false ;
this . _protocol = protocol ;
this . _connection = connection ;
this . _bufferSize = bufferSize ;
}
async _send ( message ) {
const serializedMessage = this . _protocol . writeMessage ( message ) ;
let backpressurePromise = Promise . resolve ( ) ;
// Only count invocation messages. Acks, pings, etc. don't need to be resent on reconnect
if ( this . _isInvocationMessage ( message ) ) {
this . _totalMessageCount ++ ;
let backpressurePromiseResolver = ( ) => { } ;
let backpressurePromiseRejector = ( ) => { } ;
if ( isArrayBuffer ( serializedMessage ) ) {
this . _bufferedByteCount += serializedMessage . byteLength ;
}
else {
this . _bufferedByteCount += serializedMessage . length ;
}
if ( this . _bufferedByteCount >= this . _bufferSize ) {
backpressurePromise = new Promise ( ( resolve , reject ) => {
backpressurePromiseResolver = resolve ;
backpressurePromiseRejector = reject ;
} ) ;
}
this . _messages . push ( new BufferedItem ( serializedMessage , this . _totalMessageCount , backpressurePromiseResolver , backpressurePromiseRejector ) ) ;
}
try {
// If this is set it means we are reconnecting or resending
// We don't want to send on a disconnected connection
// And we don't want to send if resend is running since that would mean sending
// this message twice
if ( ! this . _reconnectInProgress ) {
await this . _connection . send ( serializedMessage ) ;
}
}
catch {
this . _disconnected ( ) ;
}
await backpressurePromise ;
}
_ack ( ackMessage ) {
let newestAckedMessage = - 1 ;
// Find index of newest message being acked
for ( let index = 0 ; index < this . _messages . length ; index ++ ) {
const element = this . _messages [ index ] ;
if ( element . _id <= ackMessage . sequenceId ) {
newestAckedMessage = index ;
if ( isArrayBuffer ( element . _message ) ) {
this . _bufferedByteCount -= element . _message . byteLength ;
}
else {
this . _bufferedByteCount -= element . _message . length ;
}
// resolve items that have already been sent and acked
element . _resolver ( ) ;
}
else if ( this . _bufferedByteCount < this . _bufferSize ) {
// resolve items that now fall under the buffer limit but haven't been acked
element . _resolver ( ) ;
}
else {
break ;
}
}
if ( newestAckedMessage !== - 1 ) {
// We're removing everything including the message pointed to, so add 1
this . _messages = this . _messages . slice ( newestAckedMessage + 1 ) ;
}
}
_shouldProcessMessage ( message ) {
if ( this . _waitForSequenceMessage ) {
if ( message . type !== MessageType . Sequence ) {
return false ;
}
else {
this . _waitForSequenceMessage = false ;
return true ;
}
}
// No special processing for acks, pings, etc.
if ( ! this . _isInvocationMessage ( message ) ) {
return true ;
}
const currentId = this . _nextReceivingSequenceId ;
this . _nextReceivingSequenceId ++ ;
if ( currentId <= this . _latestReceivedSequenceId ) {
if ( currentId === this . _latestReceivedSequenceId ) {
// Should only hit this if we just reconnected and the server is sending
// Messages it has buffered, which would mean it hasn't seen an Ack for these messages
this . _ackTimer ( ) ;
}
// Ignore, this is a duplicate message
return false ;
}
this . _latestReceivedSequenceId = currentId ;
// Only start the timer for sending an Ack message when we have a message to ack. This also conveniently solves
// timer throttling by not having a recursive timer, and by starting the timer via a network call (recv)
this . _ackTimer ( ) ;
return true ;
}
_resetSequence ( message ) {
if ( message . sequenceId > this . _nextReceivingSequenceId ) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this . _connection . stop ( new Error ( "Sequence ID greater than amount of messages we've received." ) ) ;
return ;
}
this . _nextReceivingSequenceId = message . sequenceId ;
}
_disconnected ( ) {
this . _reconnectInProgress = true ;
this . _waitForSequenceMessage = true ;
}
async _resend ( ) {
const sequenceId = this . _messages . length !== 0
? this . _messages [ 0 ] . _id
: this . _totalMessageCount + 1 ;
await this . _connection . send ( this . _protocol . writeMessage ( { type : MessageType . Sequence , sequenceId } ) ) ;
// Get a local variable to the _messages, just in case messages are acked while resending
// Which would slice the _messages array (which creates a new copy)
const messages = this . _messages ;
for ( const element of messages ) {
await this . _connection . send ( element . _message ) ;
}
this . _reconnectInProgress = false ;
}
_dispose ( error ) {
error !== null && error !== void 0 ? error : ( error = new Error ( "Unable to reconnect to server." ) ) ;
// Unblock backpressure if any
for ( const element of this . _messages ) {
element . _rejector ( error ) ;
}
}
_isInvocationMessage ( message ) {
// There is no way to check if something implements an interface.
// So we individually check the messages in a switch statement.
// To make sure we don't miss any message types we rely on the compiler
// seeing the function returns a value and it will do the
// exhaustive check for us on the switch statement, since we don't use 'case default'
switch ( message . type ) {
case MessageType . Invocation :
case MessageType . StreamItem :
case MessageType . Completion :
case MessageType . StreamInvocation :
case MessageType . CancelInvocation :
return true ;
case MessageType . Close :
case MessageType . Sequence :
case MessageType . Ping :
case MessageType . Ack :
return false ;
}
}
_ackTimer ( ) {
if ( this . _ackTimerHandle === undefined ) {
this . _ackTimerHandle = setTimeout ( async ( ) => {
try {
if ( ! this . _reconnectInProgress ) {
await this . _connection . send ( this . _protocol . writeMessage ( { type : MessageType . Ack , sequenceId : this . _latestReceivedSequenceId } ) ) ;
}
// Ignore errors, that means the connection is closed and we don't care about the Ack message anymore.
}
catch { }
clearTimeout ( this . _ackTimerHandle ) ;
this . _ackTimerHandle = undefined ;
// 1 second delay so we don't spam Ack messages if there are many messages being received at once.
} , 1000 ) ;
}
}
}
class BufferedItem {
constructor ( message , id , resolver , rejector ) {
this . _message = message ;
this . _id = id ;
this . _resolver = resolver ;
this . _rejector = rejector ;
}
}
; // CONCATENATED MODULE: ./src/HubConnection.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
const DEFAULT _TIMEOUT _IN _MS = 30 * 1000 ;
const DEFAULT _PING _INTERVAL _IN _MS = 15 * 1000 ;
const DEFAULT _STATEFUL _RECONNECT _BUFFER _SIZE = 100000 ;
2021-07-19 01:10:52 +00:00
/** Describes the current state of the {@link HubConnection} to the server. */
var HubConnectionState ;
( function ( HubConnectionState ) {
/** The hub connection is disconnected. */
HubConnectionState [ "Disconnected" ] = "Disconnected" ;
/** The hub connection is connecting. */
HubConnectionState [ "Connecting" ] = "Connecting" ;
/** The hub connection is connected. */
HubConnectionState [ "Connected" ] = "Connected" ;
/** The hub connection is disconnecting. */
HubConnectionState [ "Disconnecting" ] = "Disconnecting" ;
/** The hub connection is reconnecting. */
HubConnectionState [ "Reconnecting" ] = "Reconnecting" ;
} ) ( HubConnectionState || ( HubConnectionState = { } ) ) ;
/** Represents a connection to a SignalR Hub. */
2024-01-17 03:00:56 +00:00
class HubConnection {
2021-07-19 01:10:52 +00:00
/** @internal */
// Using a public static factory method means we can have a private constructor and an _internal_
// create method that can be used by HubConnectionBuilder. An "internal" constructor would just
// be stripped away and the '.d.ts' file would have no constructor, which is interpreted as a
// public parameter-less constructor.
2024-01-17 03:00:56 +00:00
static create ( connection , logger , protocol , reconnectPolicy , serverTimeoutInMilliseconds , keepAliveIntervalInMilliseconds , statefulReconnectBufferSize ) {
return new HubConnection ( connection , logger , protocol , reconnectPolicy , serverTimeoutInMilliseconds , keepAliveIntervalInMilliseconds , statefulReconnectBufferSize ) ;
}
constructor ( connection , logger , protocol , reconnectPolicy , serverTimeoutInMilliseconds , keepAliveIntervalInMilliseconds , statefulReconnectBufferSize ) {
this . _nextKeepAlive = 0 ;
this . _freezeEventListener = ( ) => {
this . _logger . log ( LogLevel . Warning , "The page is being frozen, this will likely lead to the connection being closed and messages being lost. For more information see the docs at https://learn.microsoft.com/aspnet/core/signalr/javascript-client#bsleep" ) ;
} ;
Arg . isRequired ( connection , "connection" ) ;
Arg . isRequired ( logger , "logger" ) ;
Arg . isRequired ( protocol , "protocol" ) ;
this . serverTimeoutInMilliseconds = serverTimeoutInMilliseconds !== null && serverTimeoutInMilliseconds !== void 0 ? serverTimeoutInMilliseconds : DEFAULT _TIMEOUT _IN _MS ;
this . keepAliveIntervalInMilliseconds = keepAliveIntervalInMilliseconds !== null && keepAliveIntervalInMilliseconds !== void 0 ? keepAliveIntervalInMilliseconds : DEFAULT _PING _INTERVAL _IN _MS ;
this . _statefulReconnectBufferSize = statefulReconnectBufferSize !== null && statefulReconnectBufferSize !== void 0 ? statefulReconnectBufferSize : DEFAULT _STATEFUL _RECONNECT _BUFFER _SIZE ;
this . _logger = logger ;
this . _protocol = protocol ;
this . connection = connection ;
this . _reconnectPolicy = reconnectPolicy ;
this . _handshakeProtocol = new HandshakeProtocol ( ) ;
this . connection . onreceive = ( data ) => this . _processIncomingData ( data ) ;
this . connection . onclose = ( error ) => this . _connectionClosed ( error ) ;
this . _callbacks = { } ;
this . _methods = { } ;
this . _closedCallbacks = [ ] ;
this . _reconnectingCallbacks = [ ] ;
this . _reconnectedCallbacks = [ ] ;
this . _invocationId = 0 ;
this . _receivedHandshakeResponse = false ;
this . _connectionState = HubConnectionState . Disconnected ;
this . _connectionStarted = false ;
this . _cachedPingMessage = this . _protocol . writeMessage ( { type : MessageType . Ping } ) ;
}
/** Indicates the state of the {@link HubConnection} to the server. */
get state ( ) {
return this . _connectionState ;
}
/ * * R e p r e s e n t s t h e c o n n e c t i o n i d o f t h e { @ l i n k H u b C o n n e c t i o n } o n t h e s e r v e r . T h e c o n n e c t i o n i d w i l l b e n u l l w h e n t h e c o n n e c t i o n i s e i t h e r
* in the disconnected state or if the negotiation step was skipped .
* /
get connectionId ( ) {
return this . connection ? ( this . connection . connectionId || null ) : null ;
}
/** Indicates the url of the {@link HubConnection} to the server. */
get baseUrl ( ) {
return this . connection . baseUrl || "" ;
}
/ * *
* Sets a new url for the HubConnection . Note that the url can only be changed when the connection is in either the Disconnected or
* Reconnecting states .
* @ param { string } url The url to connect to .
* /
set baseUrl ( url ) {
if ( this . _connectionState !== HubConnectionState . Disconnected && this . _connectionState !== HubConnectionState . Reconnecting ) {
throw new Error ( "The HubConnection must be in the Disconnected or Reconnecting state to change the url." ) ;
}
if ( ! url ) {
throw new Error ( "The HubConnection url must be a valid url." ) ;
}
this . connection . baseUrl = url ;
}
2021-07-19 01:10:52 +00:00
/ * * S t a r t s t h e c o n n e c t i o n .
*
* @ returns { Promise < void > } A Promise that resolves when the connection has been successfully established , or rejects with an error .
* /
2024-01-17 03:00:56 +00:00
start ( ) {
this . _startPromise = this . _startWithStateTransitions ( ) ;
return this . _startPromise ;
}
async _startWithStateTransitions ( ) {
if ( this . _connectionState !== HubConnectionState . Disconnected ) {
return Promise . reject ( new Error ( "Cannot start a HubConnection that is not in the 'Disconnected' state." ) ) ;
}
this . _connectionState = HubConnectionState . Connecting ;
this . _logger . log ( LogLevel . Debug , "Starting HubConnection." ) ;
try {
await this . _startInternal ( ) ;
if ( Platform . isBrowser ) {
// Log when the browser freezes the tab so users know why their connection unexpectedly stopped working
window . document . addEventListener ( "freeze" , this . _freezeEventListener ) ;
}
this . _connectionState = HubConnectionState . Connected ;
this . _connectionStarted = true ;
this . _logger . log ( LogLevel . Debug , "HubConnection connected successfully." ) ;
}
catch ( e ) {
this . _connectionState = HubConnectionState . Disconnected ;
this . _logger . log ( LogLevel . Debug , ` HubConnection failed to start successfully because of error ' ${ e } '. ` ) ;
return Promise . reject ( e ) ;
}
}
async _startInternal ( ) {
this . _stopDuringStartError = undefined ;
this . _receivedHandshakeResponse = false ;
// Set up the promise before any connection is (re)started otherwise it could race with received messages
const handshakePromise = new Promise ( ( resolve , reject ) => {
this . _handshakeResolver = resolve ;
this . _handshakeRejecter = reject ;
2021-07-19 01:10:52 +00:00
} ) ;
2024-01-17 03:00:56 +00:00
await this . connection . start ( this . _protocol . transferFormat ) ;
try {
let version = this . _protocol . version ;
if ( ! this . connection . features . reconnect ) {
// Stateful Reconnect starts with HubProtocol version 2, newer clients connecting to older servers will fail to connect due to
// the handshake only supporting version 1, so we will try to send version 1 during the handshake to keep old servers working.
version = 1 ;
}
const handshakeRequest = {
protocol : this . _protocol . name ,
version ,
} ;
this . _logger . log ( LogLevel . Debug , "Sending handshake request." ) ;
await this . _sendMessage ( this . _handshakeProtocol . writeHandshakeRequest ( handshakeRequest ) ) ;
this . _logger . log ( LogLevel . Information , ` Using HubProtocol ' ${ this . _protocol . name } '. ` ) ;
// defensively cleanup timeout in case we receive a message from the server before we finish start
this . _cleanupTimeout ( ) ;
this . _resetTimeoutPeriod ( ) ;
this . _resetKeepAliveInterval ( ) ;
await handshakePromise ;
// It's important to check the stopDuringStartError instead of just relying on the handshakePromise
// being rejected on close, because this continuation can run after both the handshake completed successfully
// and the connection was closed.
if ( this . _stopDuringStartError ) {
// It's important to throw instead of returning a rejected promise, because we don't want to allow any state
// transitions to occur between now and the calling code observing the exceptions. Returning a rejected promise
// will cause the calling continuation to get scheduled to run later.
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw this . _stopDuringStartError ;
}
const useStatefulReconnect = this . connection . features . reconnect || false ;
if ( useStatefulReconnect ) {
this . _messageBuffer = new MessageBuffer ( this . _protocol , this . connection , this . _statefulReconnectBufferSize ) ;
this . connection . features . disconnected = this . _messageBuffer . _disconnected . bind ( this . _messageBuffer ) ;
this . connection . features . resend = ( ) => {
if ( this . _messageBuffer ) {
return this . _messageBuffer . _resend ( ) ;
}
} ;
}
if ( ! this . connection . features . inherentKeepAlive ) {
await this . _sendMessage ( this . _cachedPingMessage ) ;
}
}
catch ( e ) {
this . _logger . log ( LogLevel . Debug , ` Hub handshake failed with error ' ${ e } ' during start(). Stopping HubConnection. ` ) ;
this . _cleanupTimeout ( ) ;
this . _cleanupPingTimer ( ) ;
// HttpConnection.stop() should not complete until after the onclose callback is invoked.
// This will transition the HubConnection to the disconnected state before HttpConnection.stop() completes.
await this . connection . stop ( e ) ;
throw e ;
}
}
2021-07-19 01:10:52 +00:00
/ * * S t o p s t h e c o n n e c t i o n .
*
* @ returns { Promise < void > } A Promise that resolves when the connection has been successfully terminated , or rejects with an error .
* /
2024-01-17 03:00:56 +00:00
async stop ( ) {
// Capture the start promise before the connection might be restarted in an onclose callback.
const startPromise = this . _startPromise ;
this . connection . features . reconnect = false ;
this . _stopPromise = this . _stopInternal ( ) ;
await this . _stopPromise ;
try {
// Awaiting undefined continues immediately
await startPromise ;
}
catch ( e ) {
// This exception is returned to the user as a rejected Promise from the start method.
}
}
_stopInternal ( error ) {
if ( this . _connectionState === HubConnectionState . Disconnected ) {
this . _logger . log ( LogLevel . Debug , ` Call to HubConnection.stop( ${ error } ) ignored because it is already in the disconnected state. ` ) ;
2021-07-19 01:10:52 +00:00
return Promise . resolve ( ) ;
}
2024-01-17 03:00:56 +00:00
if ( this . _connectionState === HubConnectionState . Disconnecting ) {
this . _logger . log ( LogLevel . Debug , ` Call to HttpConnection.stop( ${ error } ) ignored because the connection is already in the disconnecting state. ` ) ;
return this . _stopPromise ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
const state = this . _connectionState ;
this . _connectionState = HubConnectionState . Disconnecting ;
this . _logger . log ( LogLevel . Debug , "Stopping HubConnection." ) ;
if ( this . _reconnectDelayHandle ) {
2021-07-19 01:10:52 +00:00
// We're in a reconnect delay which means the underlying connection is currently already stopped.
// Just clear the handle to stop the reconnect loop (which no one is waiting on thankfully) and
// fire the onclose callbacks.
2024-01-17 03:00:56 +00:00
this . _logger . log ( LogLevel . Debug , "Connection stopped during reconnect delay. Done reconnecting." ) ;
clearTimeout ( this . _reconnectDelayHandle ) ;
this . _reconnectDelayHandle = undefined ;
this . _completeClose ( ) ;
2021-07-19 01:10:52 +00:00
return Promise . resolve ( ) ;
}
2024-01-17 03:00:56 +00:00
if ( state === HubConnectionState . Connected ) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this . _sendCloseMessage ( ) ;
}
this . _cleanupTimeout ( ) ;
this . _cleanupPingTimer ( ) ;
this . _stopDuringStartError = error || new AbortError ( "The connection was stopped before the hub handshake could complete." ) ;
2021-07-19 01:10:52 +00:00
// HttpConnection.stop() should not complete until after either HttpConnection.start() fails
// or the onclose callback is invoked. The onclose callback will transition the HubConnection
// to the disconnected state if need be before HttpConnection.stop() completes.
return this . connection . stop ( error ) ;
2024-01-17 03:00:56 +00:00
}
async _sendCloseMessage ( ) {
try {
await this . _sendWithProtocol ( this . _createCloseMessage ( ) ) ;
}
catch {
// Ignore, this is a best effort attempt to let the server know the client closed gracefully.
}
}
2021-07-19 01:10:52 +00:00
/ * * I n v o k e s a s t r e a m i n g h u b m e t h o d o n t h e s e r v e r u s i n g t h e s p e c i f i e d n a m e a n d a r g u m e n t s .
*
* @ typeparam T The type of the items returned by the server .
* @ param { string } methodName The name of the server method to invoke .
* @ param { any [ ] } args The arguments used to invoke the server method .
* @ returns { IStreamResult < T > } An object that yields results from the server as they are received .
* /
2024-01-17 03:00:56 +00:00
stream ( methodName , ... args ) {
const [ streams , streamIds ] = this . _replaceStreamingParams ( args ) ;
const invocationDescriptor = this . _createStreamInvocation ( methodName , args , streamIds ) ;
// eslint-disable-next-line prefer-const
let promiseQueue ;
const subject = new Subject ( ) ;
subject . cancelCallback = ( ) => {
const cancelInvocation = this . _createCancelInvocation ( invocationDescriptor . invocationId ) ;
delete this . _callbacks [ invocationDescriptor . invocationId ] ;
return promiseQueue . then ( ( ) => {
return this . _sendWithProtocol ( cancelInvocation ) ;
2021-07-19 01:10:52 +00:00
} ) ;
} ;
2024-01-17 03:00:56 +00:00
this . _callbacks [ invocationDescriptor . invocationId ] = ( invocationEvent , error ) => {
2021-07-19 01:10:52 +00:00
if ( error ) {
subject . error ( error ) ;
return ;
}
else if ( invocationEvent ) {
// invocationEvent will not be null when an error is not passed to the callback
2024-01-17 03:00:56 +00:00
if ( invocationEvent . type === MessageType . Completion ) {
2021-07-19 01:10:52 +00:00
if ( invocationEvent . error ) {
subject . error ( new Error ( invocationEvent . error ) ) ;
}
else {
subject . complete ( ) ;
}
}
else {
subject . next ( ( invocationEvent . item ) ) ;
}
}
} ;
2024-01-17 03:00:56 +00:00
promiseQueue = this . _sendWithProtocol ( invocationDescriptor )
. catch ( ( e ) => {
2021-07-19 01:10:52 +00:00
subject . error ( e ) ;
2024-01-17 03:00:56 +00:00
delete this . _callbacks [ invocationDescriptor . invocationId ] ;
2021-07-19 01:10:52 +00:00
} ) ;
2024-01-17 03:00:56 +00:00
this . _launchStreams ( streams , promiseQueue ) ;
2021-07-19 01:10:52 +00:00
return subject ;
2024-01-17 03:00:56 +00:00
}
_sendMessage ( message ) {
this . _resetKeepAliveInterval ( ) ;
2021-07-19 01:10:52 +00:00
return this . connection . send ( message ) ;
2024-01-17 03:00:56 +00:00
}
2021-07-19 01:10:52 +00:00
/ * *
* Sends a js object to the server .
* @ param message The js object to serialize and send .
* /
2024-01-17 03:00:56 +00:00
_sendWithProtocol ( message ) {
if ( this . _messageBuffer ) {
return this . _messageBuffer . _send ( message ) ;
}
else {
return this . _sendMessage ( this . _protocol . writeMessage ( message ) ) ;
}
}
2021-07-19 01:10:52 +00:00
/ * * I n v o k e s a h u b m e t h o d o n t h e s e r v e r u s i n g t h e s p e c i f i e d n a m e a n d a r g u m e n t s . D o e s n o t w a i t f o r a r e s p o n s e f r o m t h e r e c e i v e r .
*
* The Promise returned by this method resolves when the client has sent the invocation to the server . The server may still
* be processing the invocation .
*
* @ param { string } methodName The name of the server method to invoke .
* @ param { any [ ] } args The arguments used to invoke the server method .
* @ returns { Promise < void > } A Promise that resolves when the invocation has been successfully sent , or rejects with an error .
* /
2024-01-17 03:00:56 +00:00
send ( methodName , ... args ) {
const [ streams , streamIds ] = this . _replaceStreamingParams ( args ) ;
const sendPromise = this . _sendWithProtocol ( this . _createInvocation ( methodName , args , true , streamIds ) ) ;
this . _launchStreams ( streams , sendPromise ) ;
2021-07-19 01:10:52 +00:00
return sendPromise ;
2024-01-17 03:00:56 +00:00
}
2021-07-19 01:10:52 +00:00
/ * * I n v o k e s a h u b m e t h o d o n t h e s e r v e r u s i n g t h e s p e c i f i e d n a m e a n d a r g u m e n t s .
*
* The Promise returned by this method resolves when the server indicates it has finished invoking the method . When the promise
* resolves , the server has finished invoking the method . If the server method returns a result , it is produced as the result of
* resolving the Promise .
*
* @ typeparam T The expected return type .
* @ param { string } methodName The name of the server method to invoke .
* @ param { any [ ] } args The arguments used to invoke the server method .
* @ returns { Promise < T > } A Promise that resolves with the result of the server method ( if any ) , or rejects with an error .
* /
2024-01-17 03:00:56 +00:00
invoke ( methodName , ... args ) {
const [ streams , streamIds ] = this . _replaceStreamingParams ( args ) ;
const invocationDescriptor = this . _createInvocation ( methodName , args , false , streamIds ) ;
const p = new Promise ( ( resolve , reject ) => {
2021-07-19 01:10:52 +00:00
// invocationId will always have a value for a non-blocking invocation
2024-01-17 03:00:56 +00:00
this . _callbacks [ invocationDescriptor . invocationId ] = ( invocationEvent , error ) => {
2021-07-19 01:10:52 +00:00
if ( error ) {
reject ( error ) ;
return ;
}
else if ( invocationEvent ) {
// invocationEvent will not be null when an error is not passed to the callback
2024-01-17 03:00:56 +00:00
if ( invocationEvent . type === MessageType . Completion ) {
2021-07-19 01:10:52 +00:00
if ( invocationEvent . error ) {
reject ( new Error ( invocationEvent . error ) ) ;
}
else {
resolve ( invocationEvent . result ) ;
}
}
else {
2024-01-17 03:00:56 +00:00
reject ( new Error ( ` Unexpected message type: ${ invocationEvent . type } ` ) ) ;
2021-07-19 01:10:52 +00:00
}
}
} ;
2024-01-17 03:00:56 +00:00
const promiseQueue = this . _sendWithProtocol ( invocationDescriptor )
. catch ( ( e ) => {
2021-07-19 01:10:52 +00:00
reject ( e ) ;
// invocationId will always have a value for a non-blocking invocation
2024-01-17 03:00:56 +00:00
delete this . _callbacks [ invocationDescriptor . invocationId ] ;
2021-07-19 01:10:52 +00:00
} ) ;
2024-01-17 03:00:56 +00:00
this . _launchStreams ( streams , promiseQueue ) ;
2021-07-19 01:10:52 +00:00
} ) ;
return p ;
2024-01-17 03:00:56 +00:00
}
on ( methodName , newMethod ) {
2021-07-19 01:10:52 +00:00
if ( ! methodName || ! newMethod ) {
return ;
}
methodName = methodName . toLowerCase ( ) ;
2024-01-17 03:00:56 +00:00
if ( ! this . _methods [ methodName ] ) {
this . _methods [ methodName ] = [ ] ;
2021-07-19 01:10:52 +00:00
}
// Preventing adding the same handler multiple times.
2024-01-17 03:00:56 +00:00
if ( this . _methods [ methodName ] . indexOf ( newMethod ) !== - 1 ) {
2021-07-19 01:10:52 +00:00
return ;
}
2024-01-17 03:00:56 +00:00
this . _methods [ methodName ] . push ( newMethod ) ;
}
off ( methodName , method ) {
2021-07-19 01:10:52 +00:00
if ( ! methodName ) {
return ;
}
methodName = methodName . toLowerCase ( ) ;
2024-01-17 03:00:56 +00:00
const handlers = this . _methods [ methodName ] ;
2021-07-19 01:10:52 +00:00
if ( ! handlers ) {
return ;
}
if ( method ) {
2024-01-17 03:00:56 +00:00
const removeIdx = handlers . indexOf ( method ) ;
2021-07-19 01:10:52 +00:00
if ( removeIdx !== - 1 ) {
handlers . splice ( removeIdx , 1 ) ;
if ( handlers . length === 0 ) {
2024-01-17 03:00:56 +00:00
delete this . _methods [ methodName ] ;
2021-07-19 01:10:52 +00:00
}
}
}
else {
2024-01-17 03:00:56 +00:00
delete this . _methods [ methodName ] ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
2021-07-19 01:10:52 +00:00
/ * * R e g i s t e r s a h a n d l e r t h a t w i l l b e i n v o k e d w h e n t h e c o n n e c t i o n i s c l o s e d .
*
* @ param { Function } callback The handler that will be invoked when the connection is closed . Optionally receives a single argument containing the error that caused the connection to close ( if any ) .
* /
2024-01-17 03:00:56 +00:00
onclose ( callback ) {
2021-07-19 01:10:52 +00:00
if ( callback ) {
2024-01-17 03:00:56 +00:00
this . _closedCallbacks . push ( callback ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
2021-07-19 01:10:52 +00:00
/ * * R e g i s t e r s a h a n d l e r t h a t w i l l b e i n v o k e d w h e n t h e c o n n e c t i o n s t a r t s r e c o n n e c t i n g .
*
* @ param { Function } callback The handler that will be invoked when the connection starts reconnecting . Optionally receives a single argument containing the error that caused the connection to start reconnecting ( if any ) .
* /
2024-01-17 03:00:56 +00:00
onreconnecting ( callback ) {
2021-07-19 01:10:52 +00:00
if ( callback ) {
2024-01-17 03:00:56 +00:00
this . _reconnectingCallbacks . push ( callback ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
2021-07-19 01:10:52 +00:00
/ * * R e g i s t e r s a h a n d l e r t h a t w i l l b e i n v o k e d w h e n t h e c o n n e c t i o n s u c c e s s f u l l y r e c o n n e c t s .
*
* @ param { Function } callback The handler that will be invoked when the connection successfully reconnects .
* /
2024-01-17 03:00:56 +00:00
onreconnected ( callback ) {
2021-07-19 01:10:52 +00:00
if ( callback ) {
2024-01-17 03:00:56 +00:00
this . _reconnectedCallbacks . push ( callback ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
_processIncomingData ( data ) {
this . _cleanupTimeout ( ) ;
if ( ! this . _receivedHandshakeResponse ) {
data = this . _processHandshakeResponse ( data ) ;
this . _receivedHandshakeResponse = true ;
2021-07-19 01:10:52 +00:00
}
// Data may have all been read when processing handshake response
if ( data ) {
// Parse the messages
2024-01-17 03:00:56 +00:00
const messages = this . _protocol . parseMessages ( data , this . _logger ) ;
for ( const message of messages ) {
if ( this . _messageBuffer && ! this . _messageBuffer . _shouldProcessMessage ( message ) ) {
// Don't process the message, we are either waiting for a SequenceMessage or received a duplicate message
continue ;
}
2021-07-19 01:10:52 +00:00
switch ( message . type ) {
2024-01-17 03:00:56 +00:00
case MessageType . Invocation :
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this . _invokeClientMethod ( message ) ;
2021-07-19 01:10:52 +00:00
break ;
2024-01-17 03:00:56 +00:00
case MessageType . StreamItem :
case MessageType . Completion : {
const callback = this . _callbacks [ message . invocationId ] ;
2021-07-19 01:10:52 +00:00
if ( callback ) {
2024-01-17 03:00:56 +00:00
if ( message . type === MessageType . Completion ) {
delete this . _callbacks [ message . invocationId ] ;
}
try {
callback ( message ) ;
}
catch ( e ) {
this . _logger . log ( LogLevel . Error , ` Stream callback threw error: ${ getErrorString ( e ) } ` ) ;
2021-07-19 01:10:52 +00:00
}
}
break ;
2024-01-17 03:00:56 +00:00
}
case MessageType . Ping :
2021-07-19 01:10:52 +00:00
// Don't care about pings
break ;
2024-01-17 03:00:56 +00:00
case MessageType . Close : {
this . _logger . log ( LogLevel . Information , "Close message received from server." ) ;
const error = message . error ? new Error ( "Server returned an error on close: " + message . error ) : undefined ;
2021-07-19 01:10:52 +00:00
if ( message . allowReconnect === true ) {
// It feels wrong not to await connection.stop() here, but processIncomingData is called as part of an onreceive callback which is not async,
// this is already the behavior for serverTimeout(), and HttpConnection.Stop() should catch and log all possible exceptions.
2024-01-17 03:00:56 +00:00
// eslint-disable-next-line @typescript-eslint/no-floating-promises
2021-07-19 01:10:52 +00:00
this . connection . stop ( error ) ;
}
else {
// We cannot await stopInternal() here, but subsequent calls to stop() will await this if stopInternal() is still ongoing.
2024-01-17 03:00:56 +00:00
this . _stopPromise = this . _stopInternal ( error ) ;
}
break ;
}
case MessageType . Ack :
if ( this . _messageBuffer ) {
this . _messageBuffer . _ack ( message ) ;
}
break ;
case MessageType . Sequence :
if ( this . _messageBuffer ) {
this . _messageBuffer . _resetSequence ( message ) ;
2021-07-19 01:10:52 +00:00
}
break ;
default :
2024-01-17 03:00:56 +00:00
this . _logger . log ( LogLevel . Warning , ` Invalid message type: ${ message . type } . ` ) ;
2021-07-19 01:10:52 +00:00
break ;
}
}
}
2024-01-17 03:00:56 +00:00
this . _resetTimeoutPeriod ( ) ;
}
_processHandshakeResponse ( data ) {
let responseMessage ;
let remainingData ;
2021-07-19 01:10:52 +00:00
try {
2024-01-17 03:00:56 +00:00
[ remainingData , responseMessage ] = this . _handshakeProtocol . parseHandshakeResponse ( data ) ;
2021-07-19 01:10:52 +00:00
}
catch ( e ) {
2024-01-17 03:00:56 +00:00
const message = "Error parsing handshake response: " + e ;
this . _logger . log ( LogLevel . Error , message ) ;
const error = new Error ( message ) ;
this . _handshakeRejecter ( error ) ;
2021-07-19 01:10:52 +00:00
throw error ;
}
if ( responseMessage . error ) {
2024-01-17 03:00:56 +00:00
const message = "Server returned handshake error: " + responseMessage . error ;
this . _logger . log ( LogLevel . Error , message ) ;
const error = new Error ( message ) ;
this . _handshakeRejecter ( error ) ;
2021-07-19 01:10:52 +00:00
throw error ;
}
else {
2024-01-17 03:00:56 +00:00
this . _logger . log ( LogLevel . Debug , "Server handshake complete." ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
this . _handshakeResolver ( ) ;
2021-07-19 01:10:52 +00:00
return remainingData ;
2024-01-17 03:00:56 +00:00
}
_resetKeepAliveInterval ( ) {
2021-07-19 01:10:52 +00:00
if ( this . connection . features . inherentKeepAlive ) {
return ;
}
// Set the time we want the next keep alive to be sent
// Timer will be setup on next message receive
2024-01-17 03:00:56 +00:00
this . _nextKeepAlive = new Date ( ) . getTime ( ) + this . keepAliveIntervalInMilliseconds ;
this . _cleanupPingTimer ( ) ;
}
_resetTimeoutPeriod ( ) {
2021-07-19 01:10:52 +00:00
if ( ! this . connection . features || ! this . connection . features . inherentKeepAlive ) {
// Set the timeout timer
2024-01-17 03:00:56 +00:00
this . _timeoutHandle = setTimeout ( ( ) => this . serverTimeout ( ) , this . serverTimeoutInMilliseconds ) ;
2021-07-19 01:10:52 +00:00
// Set keepAlive timer if there isn't one
2024-01-17 03:00:56 +00:00
if ( this . _pingServerHandle === undefined ) {
let nextPing = this . _nextKeepAlive - new Date ( ) . getTime ( ) ;
2021-07-19 01:10:52 +00:00
if ( nextPing < 0 ) {
nextPing = 0 ;
}
// The timer needs to be set from a networking callback to avoid Chrome timer throttling from causing timers to run once a minute
2024-01-17 03:00:56 +00:00
this . _pingServerHandle = setTimeout ( async ( ) => {
if ( this . _connectionState === HubConnectionState . Connected ) {
try {
await this . _sendMessage ( this . _cachedPingMessage ) ;
}
catch {
// We don't care about the error. It should be seen elsewhere in the client.
// The connection is probably in a bad or closed state now, cleanup the timer so it stops triggering
this . _cleanupPingTimer ( ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
} , nextPing ) ;
2021-07-19 01:10:52 +00:00
}
}
2024-01-17 03:00:56 +00:00
}
// eslint-disable-next-line @typescript-eslint/naming-convention
serverTimeout ( ) {
2021-07-19 01:10:52 +00:00
// The server hasn't talked to us in a while. It doesn't like us anymore ... :(
// Terminate the connection, but we don't need to wait on the promise. This could trigger reconnecting.
2024-01-17 03:00:56 +00:00
// eslint-disable-next-line @typescript-eslint/no-floating-promises
2021-07-19 01:10:52 +00:00
this . connection . stop ( new Error ( "Server timeout elapsed without receiving a message from the server." ) ) ;
2024-01-17 03:00:56 +00:00
}
async _invokeClientMethod ( invocationMessage ) {
const methodName = invocationMessage . target . toLowerCase ( ) ;
const methods = this . _methods [ methodName ] ;
if ( ! methods ) {
this . _logger . log ( LogLevel . Warning , ` No client method with the name ' ${ methodName } ' found. ` ) ;
// No handlers provided by client but the server is expecting a response still, so we send an error
if ( invocationMessage . invocationId ) {
this . _logger . log ( LogLevel . Warning , ` No result given for ' ${ methodName } ' method and invocation ID ' ${ invocationMessage . invocationId } '. ` ) ;
await this . _sendWithProtocol ( this . _createCompletionMessage ( invocationMessage . invocationId , "Client didn't provide a result." , null ) ) ;
}
return ;
}
// Avoid issues with handlers removing themselves thus modifying the list while iterating through it
const methodsCopy = methods . slice ( ) ;
// Server expects a response
const expectsResponse = invocationMessage . invocationId ? true : false ;
// We preserve the last result or exception but still call all handlers
let res ;
let exception ;
let completionMessage ;
for ( const m of methodsCopy ) {
2021-07-19 01:10:52 +00:00
try {
2024-01-17 03:00:56 +00:00
const prevRes = res ;
res = await m . apply ( this , invocationMessage . arguments ) ;
if ( expectsResponse && res && prevRes ) {
this . _logger . log ( LogLevel . Error , ` Multiple results provided for ' ${ methodName } '. Sending error to server. ` ) ;
completionMessage = this . _createCompletionMessage ( invocationMessage . invocationId , ` Client provided multiple results. ` , null ) ;
}
// Ignore exception if we got a result after, the exception will be logged
exception = undefined ;
2021-07-19 01:10:52 +00:00
}
catch ( e ) {
2024-01-17 03:00:56 +00:00
exception = e ;
this . _logger . log ( LogLevel . Error , ` A callback for the method ' ${ methodName } ' threw error ' ${ e } '. ` ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
if ( completionMessage ) {
await this . _sendWithProtocol ( completionMessage ) ;
}
else if ( expectsResponse ) {
// If there is an exception that means either no result was given or a handler after a result threw
if ( exception ) {
completionMessage = this . _createCompletionMessage ( invocationMessage . invocationId , ` ${ exception } ` , null ) ;
}
else if ( res !== undefined ) {
completionMessage = this . _createCompletionMessage ( invocationMessage . invocationId , null , res ) ;
}
else {
this . _logger . log ( LogLevel . Warning , ` No result given for ' ${ methodName } ' method and invocation ID ' ${ invocationMessage . invocationId } '. ` ) ;
// Client didn't provide a result or throw from a handler, server expects a response so we send an error
completionMessage = this . _createCompletionMessage ( invocationMessage . invocationId , "Client didn't provide a result." , null ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
await this . _sendWithProtocol ( completionMessage ) ;
2021-07-19 01:10:52 +00:00
}
else {
2024-01-17 03:00:56 +00:00
if ( res ) {
this . _logger . log ( LogLevel . Error , ` Result given for ' ${ methodName } ' method but server is not expecting a result. ` ) ;
}
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
_connectionClosed ( error ) {
this . _logger . log ( LogLevel . Debug , ` HubConnection.connectionClosed( ${ error } ) called while in state ${ this . _connectionState } . ` ) ;
2021-07-19 01:10:52 +00:00
// Triggering this.handshakeRejecter is insufficient because it could already be resolved without the continuation having run yet.
2024-01-17 03:00:56 +00:00
this . _stopDuringStartError = this . _stopDuringStartError || error || new AbortError ( "The underlying connection was closed before the hub handshake could complete." ) ;
2021-07-19 01:10:52 +00:00
// If the handshake is in progress, start will be waiting for the handshake promise, so we complete it.
// If it has already completed, this should just noop.
2024-01-17 03:00:56 +00:00
if ( this . _handshakeResolver ) {
this . _handshakeResolver ( ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
this . _cancelCallbacksWithError ( error || new Error ( "Invocation canceled due to the underlying connection being closed." ) ) ;
this . _cleanupTimeout ( ) ;
this . _cleanupPingTimer ( ) ;
if ( this . _connectionState === HubConnectionState . Disconnecting ) {
this . _completeClose ( error ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
else if ( this . _connectionState === HubConnectionState . Connected && this . _reconnectPolicy ) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this . _reconnect ( error ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
else if ( this . _connectionState === HubConnectionState . Connected ) {
this . _completeClose ( error ) ;
2021-07-19 01:10:52 +00:00
}
// If none of the above if conditions were true were called the HubConnection must be in either:
// 1. The Connecting state in which case the handshakeResolver will complete it and stopDuringStartError will fail it.
// 2. The Reconnecting state in which case the handshakeResolver will complete it and stopDuringStartError will fail the current reconnect attempt
// and potentially continue the reconnect() loop.
// 3. The Disconnected state in which case we're already done.
2024-01-17 03:00:56 +00:00
}
_completeClose ( error ) {
if ( this . _connectionStarted ) {
this . _connectionState = HubConnectionState . Disconnected ;
this . _connectionStarted = false ;
if ( this . _messageBuffer ) {
this . _messageBuffer . _dispose ( error !== null && error !== void 0 ? error : new Error ( "Connection closed." ) ) ;
this . _messageBuffer = undefined ;
}
if ( Platform . isBrowser ) {
window . document . removeEventListener ( "freeze" , this . _freezeEventListener ) ;
}
2021-07-19 01:10:52 +00:00
try {
2024-01-17 03:00:56 +00:00
this . _closedCallbacks . forEach ( ( c ) => c . apply ( this , [ error ] ) ) ;
2021-07-19 01:10:52 +00:00
}
catch ( e ) {
2024-01-17 03:00:56 +00:00
this . _logger . log ( LogLevel . Error , ` An onclose callback called with error ' ${ error } ' threw error ' ${ e } '. ` ) ;
2021-07-19 01:10:52 +00:00
}
}
2024-01-17 03:00:56 +00:00
}
async _reconnect ( error ) {
const reconnectStartTime = Date . now ( ) ;
let previousReconnectAttempts = 0 ;
let retryError = error !== undefined ? error : new Error ( "Attempting to reconnect due to a unknown error." ) ;
let nextRetryDelay = this . _getNextRetryDelay ( previousReconnectAttempts ++ , 0 , retryError ) ;
if ( nextRetryDelay === null ) {
this . _logger . log ( LogLevel . Debug , "Connection not reconnecting because the IRetryPolicy returned null on the first reconnect attempt." ) ;
this . _completeClose ( error ) ;
return ;
}
this . _connectionState = HubConnectionState . Reconnecting ;
if ( error ) {
this . _logger . log ( LogLevel . Information , ` Connection reconnecting because of error ' ${ error } '. ` ) ;
}
else {
this . _logger . log ( LogLevel . Information , "Connection reconnecting." ) ;
}
if ( this . _reconnectingCallbacks . length !== 0 ) {
try {
this . _reconnectingCallbacks . forEach ( ( c ) => c . apply ( this , [ error ] ) ) ;
}
catch ( e ) {
this . _logger . log ( LogLevel . Error , ` An onreconnecting callback called with error ' ${ error } ' threw error ' ${ e } '. ` ) ;
}
// Exit early if an onreconnecting callback called connection.stop().
if ( this . _connectionState !== HubConnectionState . Reconnecting ) {
this . _logger . log ( LogLevel . Debug , "Connection left the reconnecting state in onreconnecting callback. Done reconnecting." ) ;
return ;
}
}
while ( nextRetryDelay !== null ) {
this . _logger . log ( LogLevel . Information , ` Reconnect attempt number ${ previousReconnectAttempts } will start in ${ nextRetryDelay } ms. ` ) ;
await new Promise ( ( resolve ) => {
this . _reconnectDelayHandle = setTimeout ( resolve , nextRetryDelay ) ;
2021-07-19 01:10:52 +00:00
} ) ;
2024-01-17 03:00:56 +00:00
this . _reconnectDelayHandle = undefined ;
if ( this . _connectionState !== HubConnectionState . Reconnecting ) {
this . _logger . log ( LogLevel . Debug , "Connection left the reconnecting state during reconnect delay. Done reconnecting." ) ;
return ;
}
try {
await this . _startInternal ( ) ;
this . _connectionState = HubConnectionState . Connected ;
this . _logger . log ( LogLevel . Information , "HubConnection reconnected successfully." ) ;
if ( this . _reconnectedCallbacks . length !== 0 ) {
try {
this . _reconnectedCallbacks . forEach ( ( c ) => c . apply ( this , [ this . connection . connectionId ] ) ) ;
}
catch ( e ) {
this . _logger . log ( LogLevel . Error , ` An onreconnected callback called with connectionId ' ${ this . connection . connectionId } ; threw error ' ${ e } '. ` ) ;
}
}
return ;
}
catch ( e ) {
this . _logger . log ( LogLevel . Information , ` Reconnect attempt failed because of error ' ${ e } '. ` ) ;
if ( this . _connectionState !== HubConnectionState . Reconnecting ) {
this . _logger . log ( LogLevel . Debug , ` Connection moved to the ' ${ this . _connectionState } ' from the reconnecting state during reconnect attempt. Done reconnecting. ` ) ;
// The TypeScript compiler thinks that connectionState must be Connected here. The TypeScript compiler is wrong.
if ( this . _connectionState === HubConnectionState . Disconnecting ) {
this . _completeClose ( ) ;
}
return ;
}
retryError = e instanceof Error ? e : new Error ( e . toString ( ) ) ;
nextRetryDelay = this . _getNextRetryDelay ( previousReconnectAttempts ++ , Date . now ( ) - reconnectStartTime , retryError ) ;
}
}
this . _logger . log ( LogLevel . Information , ` Reconnect retries have been exhausted after ${ Date . now ( ) - reconnectStartTime } ms and ${ previousReconnectAttempts } failed attempts. Connection disconnecting. ` ) ;
this . _completeClose ( ) ;
}
_getNextRetryDelay ( previousRetryCount , elapsedMilliseconds , retryReason ) {
2021-07-19 01:10:52 +00:00
try {
2024-01-17 03:00:56 +00:00
return this . _reconnectPolicy . nextRetryDelayInMilliseconds ( {
elapsedMilliseconds ,
previousRetryCount ,
retryReason ,
2021-07-19 01:10:52 +00:00
} ) ;
}
catch ( e ) {
2024-01-17 03:00:56 +00:00
this . _logger . log ( LogLevel . Error , ` IRetryPolicy.nextRetryDelayInMilliseconds( ${ previousRetryCount } , ${ elapsedMilliseconds } ) threw error ' ${ e } '. ` ) ;
2021-07-19 01:10:52 +00:00
return null ;
}
2024-01-17 03:00:56 +00:00
}
_cancelCallbacksWithError ( error ) {
const callbacks = this . _callbacks ;
this . _callbacks = { } ;
2021-07-19 01:10:52 +00:00
Object . keys ( callbacks )
2024-01-17 03:00:56 +00:00
. forEach ( ( key ) => {
const callback = callbacks [ key ] ;
try {
callback ( null , error ) ;
}
catch ( e ) {
this . _logger . log ( LogLevel . Error , ` Stream 'error' callback called with ' ${ error } ' threw error: ${ getErrorString ( e ) } ` ) ;
}
2021-07-19 01:10:52 +00:00
} ) ;
2024-01-17 03:00:56 +00:00
}
_cleanupPingTimer ( ) {
if ( this . _pingServerHandle ) {
clearTimeout ( this . _pingServerHandle ) ;
this . _pingServerHandle = undefined ;
}
}
_cleanupTimeout ( ) {
if ( this . _timeoutHandle ) {
clearTimeout ( this . _timeoutHandle ) ;
}
}
_createInvocation ( methodName , args , nonblocking , streamIds ) {
2021-07-19 01:10:52 +00:00
if ( nonblocking ) {
if ( streamIds . length !== 0 ) {
return {
arguments : args ,
2024-01-17 03:00:56 +00:00
streamIds ,
2021-07-19 01:10:52 +00:00
target : methodName ,
2024-01-17 03:00:56 +00:00
type : MessageType . Invocation ,
2021-07-19 01:10:52 +00:00
} ;
}
else {
return {
arguments : args ,
target : methodName ,
2024-01-17 03:00:56 +00:00
type : MessageType . Invocation ,
2021-07-19 01:10:52 +00:00
} ;
}
}
else {
2024-01-17 03:00:56 +00:00
const invocationId = this . _invocationId ;
this . _invocationId ++ ;
2021-07-19 01:10:52 +00:00
if ( streamIds . length !== 0 ) {
return {
arguments : args ,
invocationId : invocationId . toString ( ) ,
2024-01-17 03:00:56 +00:00
streamIds ,
2021-07-19 01:10:52 +00:00
target : methodName ,
2024-01-17 03:00:56 +00:00
type : MessageType . Invocation ,
2021-07-19 01:10:52 +00:00
} ;
}
else {
return {
arguments : args ,
invocationId : invocationId . toString ( ) ,
target : methodName ,
2024-01-17 03:00:56 +00:00
type : MessageType . Invocation ,
2021-07-19 01:10:52 +00:00
} ;
}
}
2024-01-17 03:00:56 +00:00
}
_launchStreams ( streams , promiseQueue ) {
2021-07-19 01:10:52 +00:00
if ( streams . length === 0 ) {
return ;
}
// Synchronize stream data so they arrive in-order on the server
if ( ! promiseQueue ) {
promiseQueue = Promise . resolve ( ) ;
}
2024-01-17 03:00:56 +00:00
// We want to iterate over the keys, since the keys are the stream ids
// eslint-disable-next-line guard-for-in
for ( const streamId in streams ) {
2021-07-19 01:10:52 +00:00
streams [ streamId ] . subscribe ( {
2024-01-17 03:00:56 +00:00
complete : ( ) => {
promiseQueue = promiseQueue . then ( ( ) => this . _sendWithProtocol ( this . _createCompletionMessage ( streamId ) ) ) ;
2021-07-19 01:10:52 +00:00
} ,
2024-01-17 03:00:56 +00:00
error : ( err ) => {
let message ;
2021-07-19 01:10:52 +00:00
if ( err instanceof Error ) {
message = err . message ;
}
else if ( err && err . toString ) {
message = err . toString ( ) ;
}
else {
message = "Unknown error" ;
}
2024-01-17 03:00:56 +00:00
promiseQueue = promiseQueue . then ( ( ) => this . _sendWithProtocol ( this . _createCompletionMessage ( streamId , message ) ) ) ;
2021-07-19 01:10:52 +00:00
} ,
2024-01-17 03:00:56 +00:00
next : ( item ) => {
promiseQueue = promiseQueue . then ( ( ) => this . _sendWithProtocol ( this . _createStreamItemMessage ( streamId , item ) ) ) ;
2021-07-19 01:10:52 +00:00
} ,
} ) ;
2024-01-17 03:00:56 +00:00
}
}
_replaceStreamingParams ( args ) {
const streams = [ ] ;
const streamIds = [ ] ;
for ( let i = 0 ; i < args . length ; i ++ ) {
const argument = args [ i ] ;
if ( this . _isObservable ( argument ) ) {
const streamId = this . _invocationId ;
this . _invocationId ++ ;
2021-07-19 01:10:52 +00:00
// Store the stream for later use
streams [ streamId ] = argument ;
streamIds . push ( streamId . toString ( ) ) ;
// remove stream from args
args . splice ( i , 1 ) ;
}
}
return [ streams , streamIds ] ;
2024-01-17 03:00:56 +00:00
}
_isObservable ( arg ) {
2021-07-19 01:10:52 +00:00
// This allows other stream implementations to just work (like rxjs)
return arg && arg . subscribe && typeof arg . subscribe === "function" ;
2024-01-17 03:00:56 +00:00
}
_createStreamInvocation ( methodName , args , streamIds ) {
const invocationId = this . _invocationId ;
this . _invocationId ++ ;
2021-07-19 01:10:52 +00:00
if ( streamIds . length !== 0 ) {
return {
arguments : args ,
invocationId : invocationId . toString ( ) ,
2024-01-17 03:00:56 +00:00
streamIds ,
2021-07-19 01:10:52 +00:00
target : methodName ,
2024-01-17 03:00:56 +00:00
type : MessageType . StreamInvocation ,
2021-07-19 01:10:52 +00:00
} ;
}
else {
return {
arguments : args ,
invocationId : invocationId . toString ( ) ,
target : methodName ,
2024-01-17 03:00:56 +00:00
type : MessageType . StreamInvocation ,
2021-07-19 01:10:52 +00:00
} ;
}
}
2024-01-17 03:00:56 +00:00
_createCancelInvocation ( id ) {
return {
invocationId : id ,
type : MessageType . CancelInvocation ,
} ;
}
_createStreamItemMessage ( id , item ) {
return {
invocationId : id ,
item ,
type : MessageType . StreamItem ,
} ;
}
_createCompletionMessage ( id , error , result ) {
if ( error ) {
return {
error ,
invocationId : id ,
type : MessageType . Completion ,
} ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
return {
invocationId : id ,
result ,
type : MessageType . Completion ,
} ;
}
_createCloseMessage ( ) {
return { type : MessageType . Close } ;
}
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/DefaultReconnectPolicy.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
// 0, 2, 10, 30 second delays before reconnect attempts.
2024-01-17 03:00:56 +00:00
const DEFAULT _RETRY _DELAYS _IN _MILLISECONDS = [ 0 , 2000 , 10000 , 30000 , null ] ;
2021-07-19 01:10:52 +00:00
/** @private */
2024-01-17 03:00:56 +00:00
class DefaultReconnectPolicy {
constructor ( retryDelays ) {
this . _retryDelays = retryDelays !== undefined ? [ ... retryDelays , null ] : DEFAULT _RETRY _DELAYS _IN _MILLISECONDS ;
}
nextRetryDelayInMilliseconds ( retryContext ) {
return this . _retryDelays [ retryContext . previousRetryCount ] ;
}
}
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/HeaderNames.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
class HeaderNames {
}
HeaderNames . Authorization = "Authorization" ;
HeaderNames . Cookie = "Cookie" ;
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/AccessTokenHttpClient.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
/** @private */
class AccessTokenHttpClient extends HttpClient {
constructor ( innerClient , accessTokenFactory ) {
super ( ) ;
this . _innerClient = innerClient ;
this . _accessTokenFactory = accessTokenFactory ;
}
async send ( request ) {
let allowRetry = true ;
if ( this . _accessTokenFactory && ( ! this . _accessToken || ( request . url && request . url . indexOf ( "/negotiate?" ) > 0 ) ) ) {
// don't retry if the request is a negotiate or if we just got a potentially new token from the access token factory
allowRetry = false ;
this . _accessToken = await this . _accessTokenFactory ( ) ;
}
this . _setAuthorizationHeader ( request ) ;
const response = await this . _innerClient . send ( request ) ;
if ( allowRetry && response . statusCode === 401 && this . _accessTokenFactory ) {
this . _accessToken = await this . _accessTokenFactory ( ) ;
this . _setAuthorizationHeader ( request ) ;
return await this . _innerClient . send ( request ) ;
}
return response ;
}
_setAuthorizationHeader ( request ) {
if ( ! request . headers ) {
request . headers = { } ;
}
if ( this . _accessToken ) {
request . headers [ HeaderNames . Authorization ] = ` Bearer ${ this . _accessToken } ` ;
}
// don't remove the header if there isn't an access token factory, the user manually added the header in this case
else if ( this . _accessTokenFactory ) {
if ( request . headers [ HeaderNames . Authorization ] ) {
delete request . headers [ HeaderNames . Authorization ] ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
getCookieString ( url ) {
return this . _innerClient . getCookieString ( url ) ;
}
}
; // CONCATENATED MODULE: ./src/ITransport.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// This will be treated as a bit flag in the future, so we keep it using power-of-two values.
/** Specifies a specific HTTP transport type. */
var HttpTransportType ;
( function ( HttpTransportType ) {
/** Specifies no transport preference. */
HttpTransportType [ HttpTransportType [ "None" ] = 0 ] = "None" ;
/** Specifies the WebSockets transport. */
HttpTransportType [ HttpTransportType [ "WebSockets" ] = 1 ] = "WebSockets" ;
/** Specifies the Server-Sent Events transport. */
HttpTransportType [ HttpTransportType [ "ServerSentEvents" ] = 2 ] = "ServerSentEvents" ;
/** Specifies the Long Polling transport. */
HttpTransportType [ HttpTransportType [ "LongPolling" ] = 4 ] = "LongPolling" ;
} ) ( HttpTransportType || ( HttpTransportType = { } ) ) ;
/** Specifies the transfer format for a connection. */
var TransferFormat ;
( function ( TransferFormat ) {
/** Specifies that only text data will be transmitted over the connection. */
TransferFormat [ TransferFormat [ "Text" ] = 1 ] = "Text" ;
/** Specifies that binary data will be transmitted over the connection. */
TransferFormat [ TransferFormat [ "Binary" ] = 2 ] = "Binary" ;
} ) ( TransferFormat || ( TransferFormat = { } ) ) ;
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/AbortController.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// Rough polyfill of https://developer.mozilla.org/en-US/docs/Web/API/AbortController
// We don't actually ever use the API being polyfilled, we always use the polyfill because
// it's a very new API right now.
// Not exported from index.
/** @private */
class AbortController _AbortController {
constructor ( ) {
this . _isAborted = false ;
this . onabort = null ;
}
abort ( ) {
if ( ! this . _isAborted ) {
this . _isAborted = true ;
if ( this . onabort ) {
this . onabort ( ) ;
}
}
}
get signal ( ) {
return this ;
}
get aborted ( ) {
return this . _isAborted ;
}
}
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/LongPollingTransport.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
// Not exported from 'index', this type is internal.
2021-07-19 01:10:52 +00:00
/** @private */
2024-01-17 03:00:56 +00:00
class LongPollingTransport {
// This is an internal type, not exported from 'index' so this is really just internal.
get pollAborted ( ) {
return this . _pollAbort . aborted ;
}
constructor ( httpClient , logger , options ) {
this . _httpClient = httpClient ;
this . _logger = logger ;
this . _pollAbort = new AbortController _AbortController ( ) ;
this . _options = options ;
this . _running = false ;
this . onreceive = null ;
this . onclose = null ;
}
async connect ( url , transferFormat ) {
Arg . isRequired ( url , "url" ) ;
Arg . isRequired ( transferFormat , "transferFormat" ) ;
Arg . isIn ( transferFormat , TransferFormat , "transferFormat" ) ;
this . _url = url ;
this . _logger . log ( LogLevel . Trace , "(LongPolling transport) Connecting." ) ;
// Allow binary format on Node and Browsers that support binary content (indicated by the presence of responseType property)
if ( transferFormat === TransferFormat . Binary &&
( typeof XMLHttpRequest !== "undefined" && typeof new XMLHttpRequest ( ) . responseType !== "string" ) ) {
throw new Error ( "Binary protocols over XmlHttpRequest not implementing advanced features are not supported." ) ;
}
const [ name , value ] = getUserAgentHeader ( ) ;
const headers = { [ name ] : value , ... this . _options . headers } ;
const pollOptions = {
abortSignal : this . _pollAbort . signal ,
headers ,
timeout : 100000 ,
withCredentials : this . _options . withCredentials ,
} ;
if ( transferFormat === TransferFormat . Binary ) {
pollOptions . responseType = "arraybuffer" ;
}
// Make initial long polling request
// Server uses first long polling request to finish initializing connection and it returns without data
const pollUrl = ` ${ url } &_= ${ Date . now ( ) } ` ;
this . _logger . log ( LogLevel . Trace , ` (LongPolling transport) polling: ${ pollUrl } . ` ) ;
const response = await this . _httpClient . get ( pollUrl , pollOptions ) ;
if ( response . statusCode !== 200 ) {
this . _logger . log ( LogLevel . Error , ` (LongPolling transport) Unexpected response code: ${ response . statusCode } . ` ) ;
// Mark running as false so that the poll immediately ends and runs the close logic
this . _closeError = new HttpError ( response . statusText || "" , response . statusCode ) ;
this . _running = false ;
2021-07-19 01:10:52 +00:00
}
else {
2024-01-17 03:00:56 +00:00
this . _running = true ;
}
this . _receiving = this . _poll ( this . _url , pollOptions ) ;
}
async _poll ( url , pollOptions ) {
try {
while ( this . _running ) {
try {
const pollUrl = ` ${ url } &_= ${ Date . now ( ) } ` ;
this . _logger . log ( LogLevel . Trace , ` (LongPolling transport) polling: ${ pollUrl } . ` ) ;
const response = await this . _httpClient . get ( pollUrl , pollOptions ) ;
if ( response . statusCode === 204 ) {
this . _logger . log ( LogLevel . Information , "(LongPolling transport) Poll terminated by server." ) ;
this . _running = false ;
}
else if ( response . statusCode !== 200 ) {
this . _logger . log ( LogLevel . Error , ` (LongPolling transport) Unexpected response code: ${ response . statusCode } . ` ) ;
// Unexpected status code
this . _closeError = new HttpError ( response . statusText || "" , response . statusCode ) ;
this . _running = false ;
}
else {
// Process the response
if ( response . content ) {
this . _logger . log ( LogLevel . Trace , ` (LongPolling transport) data received. ${ getDataDetail ( response . content , this . _options . logMessageContent ) } . ` ) ;
if ( this . onreceive ) {
this . onreceive ( response . content ) ;
}
}
else {
// This is another way timeout manifest.
this . _logger . log ( LogLevel . Trace , "(LongPolling transport) Poll timed out, reissuing." ) ;
}
}
}
catch ( e ) {
if ( ! this . _running ) {
// Log but disregard errors that occur after stopping
this . _logger . log ( LogLevel . Trace , ` (LongPolling transport) Poll errored after shutdown: ${ e . message } ` ) ;
}
else {
if ( e instanceof TimeoutError ) {
// Ignore timeouts and reissue the poll.
this . _logger . log ( LogLevel . Trace , "(LongPolling transport) Poll timed out, reissuing." ) ;
}
else {
// Close the connection with the error as the result.
this . _closeError = e ;
this . _running = false ;
}
}
}
}
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
finally {
this . _logger . log ( LogLevel . Trace , "(LongPolling transport) Polling complete." ) ;
// We will reach here with pollAborted==false when the server returned a response causing the transport to stop.
// If pollAborted==true then client initiated the stop and the stop method will raise the close event after DELETE is sent.
if ( ! this . pollAborted ) {
this . _raiseOnClose ( ) ;
}
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
async send ( data ) {
if ( ! this . _running ) {
return Promise . reject ( new Error ( "Cannot send until the transport is connected" ) ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
return sendMessage ( this . _logger , "LongPolling" , this . _httpClient , this . _url , data , this . _options ) ;
}
async stop ( ) {
this . _logger . log ( LogLevel . Trace , "(LongPolling transport) Stopping polling." ) ;
// Tell receiving loop to stop, abort any current request, and then wait for it to finish
this . _running = false ;
this . _pollAbort . abort ( ) ;
try {
await this . _receiving ;
// Send DELETE to clean up long polling on the server
this . _logger . log ( LogLevel . Trace , ` (LongPolling transport) sending DELETE request to ${ this . _url } . ` ) ;
const headers = { } ;
const [ name , value ] = getUserAgentHeader ( ) ;
headers [ name ] = value ;
const deleteOptions = {
headers : { ... headers , ... this . _options . headers } ,
timeout : this . _options . timeout ,
withCredentials : this . _options . withCredentials ,
} ;
let error ;
try {
await this . _httpClient . delete ( this . _url , deleteOptions ) ;
}
catch ( err ) {
error = err ;
}
if ( error ) {
if ( error instanceof HttpError ) {
if ( error . statusCode === 404 ) {
this . _logger . log ( LogLevel . Trace , "(LongPolling transport) A 404 response was returned from sending a DELETE request." ) ;
}
else {
this . _logger . log ( LogLevel . Trace , ` (LongPolling transport) Error sending a DELETE request: ${ error } ` ) ;
}
}
}
else {
this . _logger . log ( LogLevel . Trace , "(LongPolling transport) DELETE request accepted." ) ;
2021-07-19 01:10:52 +00:00
}
}
2024-01-17 03:00:56 +00:00
finally {
this . _logger . log ( LogLevel . Trace , "(LongPolling transport) Stop finished." ) ;
// Raise close event here instead of in polling
// It needs to happen after the DELETE request is sent
this . _raiseOnClose ( ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
_raiseOnClose ( ) {
if ( this . onclose ) {
let logMessage = "(LongPolling transport) Firing onclose event." ;
if ( this . _closeError ) {
logMessage += " Error: " + this . _closeError ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
this . _logger . log ( LogLevel . Trace , logMessage ) ;
this . onclose ( this . _closeError ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
}
; // CONCATENATED MODULE: ./src/ServerSentEventsTransport.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/** @private */
class ServerSentEventsTransport {
constructor ( httpClient , accessToken , logger , options ) {
this . _httpClient = httpClient ;
this . _accessToken = accessToken ;
this . _logger = logger ;
this . _options = options ;
2021-07-19 01:10:52 +00:00
this . onreceive = null ;
this . onclose = null ;
}
2024-01-17 03:00:56 +00:00
async connect ( url , transferFormat ) {
Arg . isRequired ( url , "url" ) ;
Arg . isRequired ( transferFormat , "transferFormat" ) ;
Arg . isIn ( transferFormat , TransferFormat , "transferFormat" ) ;
this . _logger . log ( LogLevel . Trace , "(SSE transport) Connecting." ) ;
// set url before accessTokenFactory because this._url is only for send and we set the auth header instead of the query string for send
this . _url = url ;
if ( this . _accessToken ) {
url += ( url . indexOf ( "?" ) < 0 ? "?" : "&" ) + ` access_token= ${ encodeURIComponent ( this . _accessToken ) } ` ;
}
return new Promise ( ( resolve , reject ) => {
let opened = false ;
if ( transferFormat !== TransferFormat . Text ) {
reject ( new Error ( "The Server-Sent Events transport only supports the 'Text' transfer format" ) ) ;
return ;
}
let eventSource ;
if ( Platform . isBrowser || Platform . isWebWorker ) {
eventSource = new this . _options . EventSource ( url , { withCredentials : this . _options . withCredentials } ) ;
}
else {
// Non-browser passes cookies via the dictionary
const cookies = this . _httpClient . getCookieString ( url ) ;
const headers = { } ;
headers . Cookie = cookies ;
const [ name , value ] = getUserAgentHeader ( ) ;
headers [ name ] = value ;
eventSource = new this . _options . EventSource ( url , { withCredentials : this . _options . withCredentials , headers : { ... headers , ... this . _options . headers } } ) ;
}
try {
eventSource . onmessage = ( e ) => {
if ( this . onreceive ) {
try {
this . _logger . log ( LogLevel . Trace , ` (SSE transport) data received. ${ getDataDetail ( e . data , this . _options . logMessageContent ) } . ` ) ;
this . onreceive ( e . data ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
catch ( error ) {
this . _close ( error ) ;
return ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
} ;
// @ts-ignore: not using event on purpose
eventSource . onerror = ( e ) => {
// EventSource doesn't give any useful information about server side closes.
if ( opened ) {
this . _close ( ) ;
}
else {
reject ( new Error ( "EventSource failed to connect. The connection could not be found on the server,"
+ " either the connection ID is not present on the server, or a proxy is refusing/buffering the connection."
+ " If you have multiple servers check that sticky sessions are enabled." ) ) ;
}
} ;
eventSource . onopen = ( ) => {
this . _logger . log ( LogLevel . Information , ` SSE connected to ${ this . _url } ` ) ;
this . _eventSource = eventSource ;
opened = true ;
resolve ( ) ;
} ;
}
catch ( e ) {
reject ( e ) ;
return ;
}
2021-07-19 01:10:52 +00:00
} ) ;
2024-01-17 03:00:56 +00:00
}
async send ( data ) {
if ( ! this . _eventSource ) {
return Promise . reject ( new Error ( "Cannot send until the transport is connected" ) ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
return sendMessage ( this . _logger , "SSE" , this . _httpClient , this . _url , data , this . _options ) ;
}
stop ( ) {
this . _close ( ) ;
return Promise . resolve ( ) ;
}
_close ( e ) {
if ( this . _eventSource ) {
this . _eventSource . close ( ) ;
this . _eventSource = undefined ;
if ( this . onclose ) {
this . onclose ( e ) ;
}
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
}
; // CONCATENATED MODULE: ./src/WebSocketTransport.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/** @private */
class WebSocketTransport {
constructor ( httpClient , accessTokenFactory , logger , logMessageContent , webSocketConstructor , headers ) {
this . _logger = logger ;
this . _accessTokenFactory = accessTokenFactory ;
this . _logMessageContent = logMessageContent ;
this . _webSocketConstructor = webSocketConstructor ;
this . _httpClient = httpClient ;
this . onreceive = null ;
this . onclose = null ;
this . _headers = headers ;
}
async connect ( url , transferFormat ) {
Arg . isRequired ( url , "url" ) ;
Arg . isRequired ( transferFormat , "transferFormat" ) ;
Arg . isIn ( transferFormat , TransferFormat , "transferFormat" ) ;
this . _logger . log ( LogLevel . Trace , "(WebSockets transport) Connecting." ) ;
let token ;
if ( this . _accessTokenFactory ) {
token = await this . _accessTokenFactory ( ) ;
}
return new Promise ( ( resolve , reject ) => {
url = url . replace ( /^http/ , "ws" ) ;
let webSocket ;
const cookies = this . _httpClient . getCookieString ( url ) ;
let opened = false ;
if ( Platform . isNode || Platform . isReactNative ) {
const headers = { } ;
const [ name , value ] = getUserAgentHeader ( ) ;
headers [ name ] = value ;
if ( token ) {
headers [ HeaderNames . Authorization ] = ` Bearer ${ token } ` ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
if ( cookies ) {
headers [ HeaderNames . Cookie ] = cookies ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
// Only pass headers when in non-browser environments
webSocket = new this . _webSocketConstructor ( url , undefined , {
headers : { ... headers , ... this . _headers } ,
} ) ;
}
else {
if ( token ) {
url += ( url . indexOf ( "?" ) < 0 ? "?" : "&" ) + ` access_token= ${ encodeURIComponent ( token ) } ` ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
if ( ! webSocket ) {
// Chrome is not happy with passing 'undefined' as protocol
webSocket = new this . _webSocketConstructor ( url ) ;
}
if ( transferFormat === TransferFormat . Binary ) {
webSocket . binaryType = "arraybuffer" ;
}
webSocket . onopen = ( _event ) => {
this . _logger . log ( LogLevel . Information , ` WebSocket connected to ${ url } . ` ) ;
this . _webSocket = webSocket ;
opened = true ;
resolve ( ) ;
} ;
webSocket . onerror = ( event ) => {
let error = null ;
// ErrorEvent is a browser only type we need to check if the type exists before using it
if ( typeof ErrorEvent !== "undefined" && event instanceof ErrorEvent ) {
error = event . error ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
else {
error = "There was an error with the transport" ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
this . _logger . log ( LogLevel . Information , ` (WebSockets transport) ${ error } . ` ) ;
} ;
webSocket . onmessage = ( message ) => {
this . _logger . log ( LogLevel . Trace , ` (WebSockets transport) data received. ${ getDataDetail ( message . data , this . _logMessageContent ) } . ` ) ;
if ( this . onreceive ) {
try {
this . onreceive ( message . data ) ;
}
catch ( error ) {
this . _close ( error ) ;
return ;
}
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
} ;
webSocket . onclose = ( event ) => {
// Don't call close handler if connection was never established
// We'll reject the connect call instead
if ( opened ) {
this . _close ( event ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
else {
let error = null ;
// ErrorEvent is a browser only type we need to check if the type exists before using it
if ( typeof ErrorEvent !== "undefined" && event instanceof ErrorEvent ) {
error = event . error ;
2021-07-19 01:10:52 +00:00
}
else {
2024-01-17 03:00:56 +00:00
error = "WebSocket failed to connect. The connection could not be found on the server,"
+ " either the endpoint may not be a SignalR endpoint,"
+ " the connection ID is not present on the server, or there is a proxy blocking WebSockets."
+ " If you have multiple servers check that sticky sessions are enabled." ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
reject ( new Error ( error ) ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
} ;
} ) ;
}
send ( data ) {
if ( this . _webSocket && this . _webSocket . readyState === this . _webSocketConstructor . OPEN ) {
this . _logger . log ( LogLevel . Trace , ` (WebSockets transport) sending data. ${ getDataDetail ( data , this . _logMessageContent ) } . ` ) ;
this . _webSocket . send ( data ) ;
return Promise . resolve ( ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
return Promise . reject ( "WebSocket is not in the OPEN state" ) ;
}
stop ( ) {
if ( this . _webSocket ) {
// Manually invoke onclose callback inline so we know the HttpConnection was closed properly before returning
// This also solves an issue where websocket.onclose could take 18+ seconds to trigger during network disconnects
this . _close ( undefined ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
return Promise . resolve ( ) ;
}
_close ( event ) {
// webSocket will be null if the transport did not start successfully
if ( this . _webSocket ) {
// Clear websocket handlers because we are considering the socket closed now
this . _webSocket . onclose = ( ) => { } ;
this . _webSocket . onmessage = ( ) => { } ;
this . _webSocket . onerror = ( ) => { } ;
this . _webSocket . close ( ) ;
this . _webSocket = undefined ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
this . _logger . log ( LogLevel . Trace , "(WebSockets transport) socket closed." ) ;
if ( this . onclose ) {
if ( this . _isCloseEvent ( event ) && ( event . wasClean === false || event . code !== 1000 ) ) {
this . onclose ( new Error ( ` WebSocket closed with status code: ${ event . code } ( ${ event . reason || "no reason given" } ). ` ) ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
else if ( event instanceof Error ) {
this . onclose ( event ) ;
}
else {
this . onclose ( ) ;
2021-07-19 01:10:52 +00:00
}
}
}
2024-01-17 03:00:56 +00:00
_isCloseEvent ( event ) {
return event && typeof event . wasClean === "boolean" && typeof event . code === "number" ;
}
}
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/HttpConnection.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
const MAX _REDIRECTS = 100 ;
2021-07-19 01:10:52 +00:00
/** @private */
2024-01-17 03:00:56 +00:00
class HttpConnection {
constructor ( url , options = { } ) {
this . _stopPromiseResolver = ( ) => { } ;
this . features = { } ;
this . _negotiateVersion = 1 ;
Arg . isRequired ( url , "url" ) ;
this . _logger = createLogger ( options . logger ) ;
this . baseUrl = this . _resolveUrl ( url ) ;
options = options || { } ;
options . logMessageContent = options . logMessageContent === undefined ? false : options . logMessageContent ;
if ( typeof options . withCredentials === "boolean" || options . withCredentials === undefined ) {
options . withCredentials = options . withCredentials === undefined ? true : options . withCredentials ;
}
else {
throw new Error ( "withCredentials option was not a 'boolean' or 'undefined' value" ) ;
}
options . timeout = options . timeout === undefined ? 100 * 1000 : options . timeout ;
let webSocketModule = null ;
let eventSourceModule = null ;
if ( Platform . isNode && "function" !== "undefined" ) {
webSocketModule = getWS ( ) ;
eventSourceModule = getEventSource ( ) ;
}
if ( ! Platform . isNode && typeof WebSocket !== "undefined" && ! options . WebSocket ) {
options . WebSocket = WebSocket ;
}
else if ( Platform . isNode && ! options . WebSocket ) {
if ( webSocketModule ) {
options . WebSocket = webSocketModule ;
}
}
if ( ! Platform . isNode && typeof EventSource !== "undefined" && ! options . EventSource ) {
options . EventSource = EventSource ;
}
else if ( Platform . isNode && ! options . EventSource ) {
if ( typeof eventSourceModule !== "undefined" ) {
options . EventSource = eventSourceModule ;
}
}
this . _httpClient = new AccessTokenHttpClient ( options . httpClient || new DefaultHttpClient ( this . _logger ) , options . accessTokenFactory ) ;
this . _connectionState = "Disconnected" /* ConnectionState.Disconnected */ ;
this . _connectionStarted = false ;
this . _options = options ;
2021-07-19 01:10:52 +00:00
this . onreceive = null ;
this . onclose = null ;
}
2024-01-17 03:00:56 +00:00
async start ( transferFormat ) {
transferFormat = transferFormat || TransferFormat . Binary ;
Arg . isIn ( transferFormat , TransferFormat , "transferFormat" ) ;
this . _logger . log ( LogLevel . Debug , ` Starting connection with transfer format ' ${ TransferFormat [ transferFormat ] } '. ` ) ;
if ( this . _connectionState !== "Disconnected" /* ConnectionState.Disconnected */ ) {
return Promise . reject ( new Error ( "Cannot start an HttpConnection that is not in the 'Disconnected' state." ) ) ;
}
this . _connectionState = "Connecting" /* ConnectionState.Connecting */ ;
this . _startInternalPromise = this . _startInternal ( transferFormat ) ;
await this . _startInternalPromise ;
// The TypeScript compiler thinks that connectionState must be Connecting here. The TypeScript compiler is wrong.
if ( this . _connectionState === "Disconnecting" /* ConnectionState.Disconnecting */ ) {
// stop() was called and transitioned the client into the Disconnecting state.
const message = "Failed to start the HttpConnection before stop() was called." ;
this . _logger . log ( LogLevel . Error , message ) ;
// We cannot await stopPromise inside startInternal since stopInternal awaits the startInternalPromise.
await this . _stopPromise ;
return Promise . reject ( new AbortError ( message ) ) ;
}
else if ( this . _connectionState !== "Connected" /* ConnectionState.Connected */ ) {
// stop() was called and transitioned the client into the Disconnecting state.
const message = "HttpConnection.startInternal completed gracefully but didn't enter the connection into the connected state!" ;
this . _logger . log ( LogLevel . Error , message ) ;
return Promise . reject ( new AbortError ( message ) ) ;
}
this . _connectionStarted = true ;
}
send ( data ) {
if ( this . _connectionState !== "Connected" /* ConnectionState.Connected */ ) {
return Promise . reject ( new Error ( "Cannot send data if the connection is not in the 'Connected' State." ) ) ;
}
if ( ! this . _sendQueue ) {
this . _sendQueue = new TransportSendQueue ( this . transport ) ;
}
// Transport will not be null if state is connected
return this . _sendQueue . send ( data ) ;
}
async stop ( error ) {
if ( this . _connectionState === "Disconnected" /* ConnectionState.Disconnected */ ) {
this . _logger . log ( LogLevel . Debug , ` Call to HttpConnection.stop( ${ error } ) ignored because the connection is already in the disconnected state. ` ) ;
return Promise . resolve ( ) ;
}
if ( this . _connectionState === "Disconnecting" /* ConnectionState.Disconnecting */ ) {
this . _logger . log ( LogLevel . Debug , ` Call to HttpConnection.stop( ${ error } ) ignored because the connection is already in the disconnecting state. ` ) ;
return this . _stopPromise ;
}
this . _connectionState = "Disconnecting" /* ConnectionState.Disconnecting */ ;
this . _stopPromise = new Promise ( ( resolve ) => {
// Don't complete stop() until stopConnection() completes.
this . _stopPromiseResolver = resolve ;
2021-07-19 01:10:52 +00:00
} ) ;
2024-01-17 03:00:56 +00:00
// stopInternal should never throw so just observe it.
await this . _stopInternal ( error ) ;
await this . _stopPromise ;
}
async _stopInternal ( error ) {
// Set error as soon as possible otherwise there is a race between
// the transport closing and providing an error and the error from a close message
// We would prefer the close message error.
this . _stopError = error ;
try {
await this . _startInternalPromise ;
}
catch ( e ) {
// This exception is returned to the user as a rejected Promise from the start method.
}
// The transport's onclose will trigger stopConnection which will run our onclose event.
// The transport should always be set if currently connected. If it wasn't set, it's likely because
// stop was called during start() and start() failed.
if ( this . transport ) {
try {
await this . transport . stop ( ) ;
}
catch ( e ) {
this . _logger . log ( LogLevel . Error , ` HttpConnection.transport.stop() threw error ' ${ e } '. ` ) ;
this . _stopConnection ( ) ;
}
this . transport = undefined ;
}
else {
this . _logger . log ( LogLevel . Debug , "HttpConnection.transport is undefined in HttpConnection.stop() because start() failed." ) ;
}
}
async _startInternal ( transferFormat ) {
// Store the original base url and the access token factory since they may change
// as part of negotiating
let url = this . baseUrl ;
this . _accessTokenFactory = this . _options . accessTokenFactory ;
this . _httpClient . _accessTokenFactory = this . _accessTokenFactory ;
try {
if ( this . _options . skipNegotiation ) {
if ( this . _options . transport === HttpTransportType . WebSockets ) {
// No need to add a connection ID in this case
this . transport = this . _constructTransport ( HttpTransportType . WebSockets ) ;
// We should just call connect directly in this case.
// No fallback or negotiate in this case.
await this . _startTransport ( url , transferFormat ) ;
}
else {
throw new Error ( "Negotiation can only be skipped when using the WebSocket transport directly." ) ;
}
}
else {
let negotiateResponse = null ;
let redirects = 0 ;
do {
negotiateResponse = await this . _getNegotiationResponse ( url ) ;
// the user tries to stop the connection when it is being started
if ( this . _connectionState === "Disconnecting" /* ConnectionState.Disconnecting */ || this . _connectionState === "Disconnected" /* ConnectionState.Disconnected */ ) {
throw new AbortError ( "The connection was stopped during negotiation." ) ;
}
if ( negotiateResponse . error ) {
throw new Error ( negotiateResponse . error ) ;
}
if ( negotiateResponse . ProtocolVersion ) {
throw new Error ( "Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details." ) ;
}
if ( negotiateResponse . url ) {
url = negotiateResponse . url ;
}
if ( negotiateResponse . accessToken ) {
// Replace the current access token factory with one that uses
// the returned access token
const accessToken = negotiateResponse . accessToken ;
this . _accessTokenFactory = ( ) => accessToken ;
// set the factory to undefined so the AccessTokenHttpClient won't retry with the same token, since we know it won't change until a connection restart
this . _httpClient . _accessToken = accessToken ;
this . _httpClient . _accessTokenFactory = undefined ;
}
redirects ++ ;
} while ( negotiateResponse . url && redirects < MAX _REDIRECTS ) ;
if ( redirects === MAX _REDIRECTS && negotiateResponse . url ) {
throw new Error ( "Negotiate redirection limit exceeded." ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
await this . _createTransport ( url , this . _options . transport , negotiateResponse , transferFormat ) ;
}
if ( this . transport instanceof LongPollingTransport ) {
this . features . inherentKeepAlive = true ;
}
if ( this . _connectionState === "Connecting" /* ConnectionState.Connecting */ ) {
// Ensure the connection transitions to the connected state prior to completing this.startInternalPromise.
// start() will handle the case when stop was called and startInternal exits still in the disconnecting state.
this . _logger . log ( LogLevel . Debug , "The HttpConnection connected successfully." ) ;
this . _connectionState = "Connected" /* ConnectionState.Connected */ ;
}
// stop() is waiting on us via this.startInternalPromise so keep this.transport around so it can clean up.
// This is the only case startInternal can exit in neither the connected nor disconnected state because stopConnection()
// will transition to the disconnected state. start() will wait for the transition using the stopPromise.
}
catch ( e ) {
this . _logger . log ( LogLevel . Error , "Failed to start the connection: " + e ) ;
this . _connectionState = "Disconnected" /* ConnectionState.Disconnected */ ;
this . transport = undefined ;
// if start fails, any active calls to stop assume that start will complete the stop promise
this . _stopPromiseResolver ( ) ;
return Promise . reject ( e ) ;
}
}
async _getNegotiationResponse ( url ) {
const headers = { } ;
const [ name , value ] = getUserAgentHeader ( ) ;
headers [ name ] = value ;
const negotiateUrl = this . _resolveNegotiateUrl ( url ) ;
this . _logger . log ( LogLevel . Debug , ` Sending negotiation request: ${ negotiateUrl } . ` ) ;
try {
const response = await this . _httpClient . post ( negotiateUrl , {
content : "" ,
headers : { ... headers , ... this . _options . headers } ,
timeout : this . _options . timeout ,
withCredentials : this . _options . withCredentials ,
2021-07-19 01:10:52 +00:00
} ) ;
2024-01-17 03:00:56 +00:00
if ( response . statusCode !== 200 ) {
return Promise . reject ( new Error ( ` Unexpected status code returned from negotiate ' ${ response . statusCode } ' ` ) ) ;
}
const negotiateResponse = JSON . parse ( response . content ) ;
if ( ! negotiateResponse . negotiateVersion || negotiateResponse . negotiateVersion < 1 ) {
// Negotiate version 0 doesn't use connectionToken
// So we set it equal to connectionId so all our logic can use connectionToken without being aware of the negotiate version
negotiateResponse . connectionToken = negotiateResponse . connectionId ;
}
if ( negotiateResponse . useStatefulReconnect && this . _options . _useStatefulReconnect !== true ) {
return Promise . reject ( new FailedToNegotiateWithServerError ( "Client didn't negotiate Stateful Reconnect but the server did." ) ) ;
}
return negotiateResponse ;
}
catch ( e ) {
let errorMessage = "Failed to complete negotiation with the server: " + e ;
if ( e instanceof HttpError ) {
if ( e . statusCode === 404 ) {
errorMessage = errorMessage + " Either this is not a SignalR endpoint or there is a proxy blocking the connection." ;
}
}
this . _logger . log ( LogLevel . Error , errorMessage ) ;
return Promise . reject ( new FailedToNegotiateWithServerError ( errorMessage ) ) ;
}
}
_createConnectUrl ( url , connectionToken ) {
if ( ! connectionToken ) {
return url ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
return url + ( url . indexOf ( "?" ) === - 1 ? "?" : "&" ) + ` id= ${ connectionToken } ` ;
}
async _createTransport ( url , requestedTransport , negotiateResponse , requestedTransferFormat ) {
let connectUrl = this . _createConnectUrl ( url , negotiateResponse . connectionToken ) ;
if ( this . _isITransport ( requestedTransport ) ) {
this . _logger . log ( LogLevel . Debug , "Connection was provided an instance of ITransport, using that directly." ) ;
this . transport = requestedTransport ;
await this . _startTransport ( connectUrl , requestedTransferFormat ) ;
this . connectionId = negotiateResponse . connectionId ;
2021-07-19 01:10:52 +00:00
return ;
}
2024-01-17 03:00:56 +00:00
const transportExceptions = [ ] ;
const transports = negotiateResponse . availableTransports || [ ] ;
let negotiate = negotiateResponse ;
for ( const endpoint of transports ) {
const transportOrError = this . _resolveTransportOrError ( endpoint , requestedTransport , requestedTransferFormat , ( negotiate === null || negotiate === void 0 ? void 0 : negotiate . useStatefulReconnect ) === true ) ;
if ( transportOrError instanceof Error ) {
// Store the error and continue, we don't want to cause a re-negotiate in these cases
transportExceptions . push ( ` ${ endpoint . transport } failed: ` ) ;
transportExceptions . push ( transportOrError ) ;
}
else if ( this . _isITransport ( transportOrError ) ) {
this . transport = transportOrError ;
if ( ! negotiate ) {
try {
negotiate = await this . _getNegotiationResponse ( url ) ;
}
catch ( ex ) {
return Promise . reject ( ex ) ;
}
connectUrl = this . _createConnectUrl ( url , negotiate . connectionToken ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
try {
await this . _startTransport ( connectUrl , requestedTransferFormat ) ;
this . connectionId = negotiate . connectionId ;
return ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
catch ( ex ) {
this . _logger . log ( LogLevel . Error , ` Failed to start the transport ' ${ endpoint . transport } ': ${ ex } ` ) ;
negotiate = undefined ;
transportExceptions . push ( new FailedToStartTransportError ( ` ${ endpoint . transport } failed: ${ ex } ` , HttpTransportType [ endpoint . transport ] ) ) ;
if ( this . _connectionState !== "Connecting" /* ConnectionState.Connecting */ ) {
const message = "Failed to select transport before stop() was called." ;
this . _logger . log ( LogLevel . Debug , message ) ;
return Promise . reject ( new AbortError ( message ) ) ;
}
2021-07-19 01:10:52 +00:00
}
}
}
2024-01-17 03:00:56 +00:00
if ( transportExceptions . length > 0 ) {
return Promise . reject ( new AggregateErrors ( ` Unable to connect to the server with any of the available transports. ${ transportExceptions . join ( " " ) } ` , transportExceptions ) ) ;
}
return Promise . reject ( new Error ( "None of the transports supported by the client are supported by the server." ) ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
_constructTransport ( transport ) {
switch ( transport ) {
case HttpTransportType . WebSockets :
if ( ! this . _options . WebSocket ) {
throw new Error ( "'WebSocket' is not supported in your environment." ) ;
}
return new WebSocketTransport ( this . _httpClient , this . _accessTokenFactory , this . _logger , this . _options . logMessageContent , this . _options . WebSocket , this . _options . headers || { } ) ;
case HttpTransportType . ServerSentEvents :
if ( ! this . _options . EventSource ) {
throw new Error ( "'EventSource' is not supported in your environment." ) ;
}
return new ServerSentEventsTransport ( this . _httpClient , this . _httpClient . _accessToken , this . _logger , this . _options ) ;
case HttpTransportType . LongPolling :
return new LongPollingTransport ( this . _httpClient , this . _logger , this . _options ) ;
default :
throw new Error ( ` Unknown transport: ${ transport } . ` ) ;
2021-07-19 01:10:52 +00:00
}
}
2024-01-17 03:00:56 +00:00
_startTransport ( url , transferFormat ) {
this . transport . onreceive = this . onreceive ;
if ( this . features . reconnect ) {
this . transport . onclose = async ( e ) => {
let callStop = false ;
if ( this . features . reconnect ) {
try {
this . features . disconnected ( ) ;
await this . transport . connect ( url , transferFormat ) ;
await this . features . resend ( ) ;
}
catch {
callStop = true ;
}
}
else {
this . _stopConnection ( e ) ;
return ;
}
if ( callStop ) {
this . _stopConnection ( e ) ;
}
} ;
}
else {
this . transport . onclose = ( e ) => this . _stopConnection ( e ) ;
}
return this . transport . connect ( url , transferFormat ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
_resolveTransportOrError ( endpoint , requestedTransport , requestedTransferFormat , useStatefulReconnect ) {
const transport = HttpTransportType [ endpoint . transport ] ;
if ( transport === null || transport === undefined ) {
this . _logger . log ( LogLevel . Debug , ` Skipping transport ' ${ endpoint . transport } ' because it is not supported by this client. ` ) ;
return new Error ( ` Skipping transport ' ${ endpoint . transport } ' because it is not supported by this client. ` ) ;
}
else {
if ( transportMatches ( requestedTransport , transport ) ) {
const transferFormats = endpoint . transferFormats . map ( ( s ) => TransferFormat [ s ] ) ;
if ( transferFormats . indexOf ( requestedTransferFormat ) >= 0 ) {
if ( ( transport === HttpTransportType . WebSockets && ! this . _options . WebSocket ) ||
( transport === HttpTransportType . ServerSentEvents && ! this . _options . EventSource ) ) {
this . _logger . log ( LogLevel . Debug , ` Skipping transport ' ${ HttpTransportType [ transport ] } ' because it is not supported in your environment.' ` ) ;
return new UnsupportedTransportError ( ` ' ${ HttpTransportType [ transport ] } ' is not supported in your environment. ` , transport ) ;
}
else {
this . _logger . log ( LogLevel . Debug , ` Selecting transport ' ${ HttpTransportType [ transport ] } '. ` ) ;
try {
this . features . reconnect = transport === HttpTransportType . WebSockets ? useStatefulReconnect : undefined ;
return this . _constructTransport ( transport ) ;
}
catch ( ex ) {
return ex ;
}
}
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
else {
this . _logger . log ( LogLevel . Debug , ` Skipping transport ' ${ HttpTransportType [ transport ] } ' because it does not support the requested transfer format ' ${ TransferFormat [ requestedTransferFormat ] } '. ` ) ;
return new Error ( ` ' ${ HttpTransportType [ transport ] } ' does not support ${ TransferFormat [ requestedTransferFormat ] } . ` ) ;
2021-07-19 01:10:52 +00:00
}
}
2024-01-17 03:00:56 +00:00
else {
this . _logger . log ( LogLevel . Debug , ` Skipping transport ' ${ HttpTransportType [ transport ] } ' because it was disabled by the client. ` ) ;
return new DisabledTransportError ( ` ' ${ HttpTransportType [ transport ] } ' is disabled by the client. ` , transport ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
_isITransport ( transport ) {
return transport && typeof ( transport ) === "object" && "connect" in transport ;
}
_stopConnection ( error ) {
this . _logger . log ( LogLevel . Debug , ` HttpConnection.stopConnection( ${ error } ) called while in state ${ this . _connectionState } . ` ) ;
this . transport = undefined ;
// If we have a stopError, it takes precedence over the error from the transport
error = this . _stopError || error ;
this . _stopError = undefined ;
if ( this . _connectionState === "Disconnected" /* ConnectionState.Disconnected */ ) {
this . _logger . log ( LogLevel . Debug , ` Call to HttpConnection.stopConnection( ${ error } ) was ignored because the connection is already in the disconnected state. ` ) ;
return ;
}
if ( this . _connectionState === "Connecting" /* ConnectionState.Connecting */ ) {
this . _logger . log ( LogLevel . Warning , ` Call to HttpConnection.stopConnection( ${ error } ) was ignored because the connection is still in the connecting state. ` ) ;
throw new Error ( ` HttpConnection.stopConnection( ${ error } ) was called while the connection is still in the connecting state. ` ) ;
}
if ( this . _connectionState === "Disconnecting" /* ConnectionState.Disconnecting */ ) {
// A call to stop() induced this call to stopConnection and needs to be completed.
// Any stop() awaiters will be scheduled to continue after the onclose callback fires.
this . _stopPromiseResolver ( ) ;
}
if ( error ) {
this . _logger . log ( LogLevel . Error , ` Connection disconnected with error ' ${ error } '. ` ) ;
}
else {
this . _logger . log ( LogLevel . Information , "Connection disconnected." ) ;
}
if ( this . _sendQueue ) {
this . _sendQueue . stop ( ) . catch ( ( e ) => {
this . _logger . log ( LogLevel . Error , ` TransportSendQueue.stop() threw error ' ${ e } '. ` ) ;
2021-07-19 01:10:52 +00:00
} ) ;
2024-01-17 03:00:56 +00:00
this . _sendQueue = undefined ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
this . connectionId = undefined ;
this . _connectionState = "Disconnected" /* ConnectionState.Disconnected */ ;
if ( this . _connectionStarted ) {
this . _connectionStarted = false ;
try {
if ( this . onclose ) {
this . onclose ( error ) ;
}
}
catch ( e ) {
this . _logger . log ( LogLevel . Error , ` HttpConnection.onclose( ${ error } ) threw error ' ${ e } '. ` ) ;
}
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
_resolveUrl ( url ) {
// startsWith is not supported in IE
if ( url . lastIndexOf ( "https://" , 0 ) === 0 || url . lastIndexOf ( "http://" , 0 ) === 0 ) {
return url ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
if ( ! Platform . isBrowser ) {
throw new Error ( ` Cannot resolve ' ${ url } '. ` ) ;
}
// Setting the url to the href propery of an anchor tag handles normalization
// for us. There are 3 main cases.
// 1. Relative path normalization e.g "b" -> "http://localhost:5000/a/b"
// 2. Absolute path normalization e.g "/a/b" -> "http://localhost:5000/a/b"
// 3. Networkpath reference normalization e.g "//localhost:5000/a/b" -> "http://localhost:5000/a/b"
const aTag = window . document . createElement ( "a" ) ;
aTag . href = url ;
this . _logger . log ( LogLevel . Information , ` Normalizing ' ${ url } ' to ' ${ aTag . href } '. ` ) ;
return aTag . href ;
}
_resolveNegotiateUrl ( url ) {
const negotiateUrl = new URL ( url ) ;
if ( negotiateUrl . pathname . endsWith ( '/' ) ) {
negotiateUrl . pathname += "negotiate" ;
}
else {
negotiateUrl . pathname += "/negotiate" ;
}
const searchParams = new URLSearchParams ( negotiateUrl . searchParams ) ;
if ( ! searchParams . has ( "negotiateVersion" ) ) {
searchParams . append ( "negotiateVersion" , this . _negotiateVersion . toString ( ) ) ;
}
if ( searchParams . has ( "useStatefulReconnect" ) ) {
if ( searchParams . get ( "useStatefulReconnect" ) === "true" ) {
this . _options . _useStatefulReconnect = true ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
else if ( this . _options . _useStatefulReconnect === true ) {
searchParams . append ( "useStatefulReconnect" , "true" ) ;
}
negotiateUrl . search = searchParams . toString ( ) ;
return negotiateUrl . toString ( ) ;
}
}
function transportMatches ( requestedTransport , actualTransport ) {
return ! requestedTransport || ( ( actualTransport & requestedTransport ) !== 0 ) ;
}
/** @private */
class TransportSendQueue {
constructor ( _transport ) {
this . _transport = _transport ;
this . _buffer = [ ] ;
this . _executing = true ;
this . _sendBufferedData = new PromiseSource ( ) ;
this . _transportResult = new PromiseSource ( ) ;
this . _sendLoopPromise = this . _sendLoop ( ) ;
}
send ( data ) {
this . _bufferData ( data ) ;
if ( ! this . _transportResult ) {
this . _transportResult = new PromiseSource ( ) ;
}
return this . _transportResult . promise ;
}
stop ( ) {
this . _executing = false ;
this . _sendBufferedData . resolve ( ) ;
return this . _sendLoopPromise ;
}
_bufferData ( data ) {
if ( this . _buffer . length && typeof ( this . _buffer [ 0 ] ) !== typeof ( data ) ) {
throw new Error ( ` Expected data to be of type ${ typeof ( this . _buffer ) } but was of type ${ typeof ( data ) } ` ) ;
}
this . _buffer . push ( data ) ;
this . _sendBufferedData . resolve ( ) ;
}
async _sendLoop ( ) {
while ( true ) {
await this . _sendBufferedData . promise ;
if ( ! this . _executing ) {
if ( this . _transportResult ) {
this . _transportResult . reject ( "Connection stopped." ) ;
}
break ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
this . _sendBufferedData = new PromiseSource ( ) ;
const transportResult = this . _transportResult ;
this . _transportResult = undefined ;
const data = typeof ( this . _buffer [ 0 ] ) === "string" ?
this . _buffer . join ( "" ) :
TransportSendQueue . _concatBuffers ( this . _buffer ) ;
this . _buffer . length = 0 ;
try {
await this . _transport . send ( data ) ;
transportResult . resolve ( ) ;
}
catch ( error ) {
transportResult . reject ( error ) ;
2021-07-19 01:10:52 +00:00
}
}
2024-01-17 03:00:56 +00:00
}
static _concatBuffers ( arrayBuffers ) {
const totalLength = arrayBuffers . map ( ( b ) => b . byteLength ) . reduce ( ( a , b ) => a + b ) ;
const result = new Uint8Array ( totalLength ) ;
let offset = 0 ;
for ( const item of arrayBuffers ) {
result . set ( new Uint8Array ( item ) , offset ) ;
offset += item . byteLength ;
}
return result . buffer ;
}
}
class PromiseSource {
constructor ( ) {
this . promise = new Promise ( ( resolve , reject ) => [ this . _resolver , this . _rejecter ] = [ resolve , reject ] ) ;
}
resolve ( ) {
this . _resolver ( ) ;
}
reject ( reason ) {
this . _rejecter ( reason ) ;
}
}
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/JsonHubProtocol.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
const JSON _HUB _PROTOCOL _NAME = "json" ;
2021-07-19 01:10:52 +00:00
/** Implements the JSON Hub Protocol. */
2024-01-17 03:00:56 +00:00
class JsonHubProtocol {
constructor ( ) {
2021-07-19 01:10:52 +00:00
/** @inheritDoc */
this . name = JSON _HUB _PROTOCOL _NAME ;
/** @inheritDoc */
2024-01-17 03:00:56 +00:00
this . version = 2 ;
2021-07-19 01:10:52 +00:00
/** @inheritDoc */
2024-01-17 03:00:56 +00:00
this . transferFormat = TransferFormat . Text ;
2021-07-19 01:10:52 +00:00
}
/ * * C r e a t e s a n a r r a y o f { @ l i n k @ m i c r o s o f t / s i g n a l r . H u b M e s s a g e } o b j e c t s f r o m t h e s p e c i f i e d s e r i a l i z e d r e p r e s e n t a t i o n .
*
* @ param { string } input A string containing the serialized representation .
* @ param { ILogger } logger A logger that will be used to log messages that occur during parsing .
* /
2024-01-17 03:00:56 +00:00
parseMessages ( input , logger ) {
2021-07-19 01:10:52 +00:00
// The interface does allow "ArrayBuffer" to be passed in, but this implementation does not. So let's throw a useful error.
if ( typeof input !== "string" ) {
throw new Error ( "Invalid input for JSON hub protocol. Expected a string." ) ;
}
if ( ! input ) {
return [ ] ;
}
if ( logger === null ) {
2024-01-17 03:00:56 +00:00
logger = NullLogger . instance ;
2021-07-19 01:10:52 +00:00
}
// Parse the messages
2024-01-17 03:00:56 +00:00
const messages = TextMessageFormat . parse ( input ) ;
const hubMessages = [ ] ;
for ( const message of messages ) {
const parsedMessage = JSON . parse ( message ) ;
2021-07-19 01:10:52 +00:00
if ( typeof parsedMessage . type !== "number" ) {
throw new Error ( "Invalid payload." ) ;
}
switch ( parsedMessage . type ) {
2024-01-17 03:00:56 +00:00
case MessageType . Invocation :
this . _isInvocationMessage ( parsedMessage ) ;
2021-07-19 01:10:52 +00:00
break ;
2024-01-17 03:00:56 +00:00
case MessageType . StreamItem :
this . _isStreamItemMessage ( parsedMessage ) ;
2021-07-19 01:10:52 +00:00
break ;
2024-01-17 03:00:56 +00:00
case MessageType . Completion :
this . _isCompletionMessage ( parsedMessage ) ;
2021-07-19 01:10:52 +00:00
break ;
2024-01-17 03:00:56 +00:00
case MessageType . Ping :
2021-07-19 01:10:52 +00:00
// Single value, no need to validate
break ;
2024-01-17 03:00:56 +00:00
case MessageType . Close :
2021-07-19 01:10:52 +00:00
// All optional values, no need to validate
break ;
2024-01-17 03:00:56 +00:00
case MessageType . Ack :
this . _isAckMessage ( parsedMessage ) ;
break ;
case MessageType . Sequence :
this . _isSequenceMessage ( parsedMessage ) ;
break ;
2021-07-19 01:10:52 +00:00
default :
// Future protocol changes can add message types, old clients can ignore them
2024-01-17 03:00:56 +00:00
logger . log ( LogLevel . Information , "Unknown message type '" + parsedMessage . type + "' ignored." ) ;
2021-07-19 01:10:52 +00:00
continue ;
}
hubMessages . push ( parsedMessage ) ;
}
return hubMessages ;
2024-01-17 03:00:56 +00:00
}
2021-07-19 01:10:52 +00:00
/ * * W r i t e s t h e s p e c i f i e d { @ l i n k @ m i c r o s o f t / s i g n a l r . H u b M e s s a g e } t o a s t r i n g a n d r e t u r n s i t .
*
* @ param { HubMessage } message The message to write .
* @ returns { string } A string containing the serialized representation of the message .
* /
2024-01-17 03:00:56 +00:00
writeMessage ( message ) {
return TextMessageFormat . write ( JSON . stringify ( message ) ) ;
}
_isInvocationMessage ( message ) {
this . _assertNotEmptyString ( message . target , "Invalid payload for Invocation message." ) ;
2021-07-19 01:10:52 +00:00
if ( message . invocationId !== undefined ) {
2024-01-17 03:00:56 +00:00
this . _assertNotEmptyString ( message . invocationId , "Invalid payload for Invocation message." ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
_isStreamItemMessage ( message ) {
this . _assertNotEmptyString ( message . invocationId , "Invalid payload for StreamItem message." ) ;
2021-07-19 01:10:52 +00:00
if ( message . item === undefined ) {
throw new Error ( "Invalid payload for StreamItem message." ) ;
}
2024-01-17 03:00:56 +00:00
}
_isCompletionMessage ( message ) {
2021-07-19 01:10:52 +00:00
if ( message . result && message . error ) {
throw new Error ( "Invalid payload for Completion message." ) ;
}
if ( ! message . result && message . error ) {
2024-01-17 03:00:56 +00:00
this . _assertNotEmptyString ( message . error , "Invalid payload for Completion message." ) ;
}
this . _assertNotEmptyString ( message . invocationId , "Invalid payload for Completion message." ) ;
}
_isAckMessage ( message ) {
if ( typeof message . sequenceId !== 'number' ) {
throw new Error ( "Invalid SequenceId for Ack message." ) ;
2021-07-19 01:10:52 +00:00
}
2024-01-17 03:00:56 +00:00
}
_isSequenceMessage ( message ) {
if ( typeof message . sequenceId !== 'number' ) {
throw new Error ( "Invalid SequenceId for Sequence message." ) ;
}
}
_assertNotEmptyString ( value , errorMessage ) {
2021-07-19 01:10:52 +00:00
if ( typeof value !== "string" || value === "" ) {
throw new Error ( errorMessage ) ;
}
2024-01-17 03:00:56 +00:00
}
}
; // CONCATENATED MODULE: ./src/HubConnectionBuilder.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
const LogLevelNameMapping = {
trace : LogLevel . Trace ,
debug : LogLevel . Debug ,
info : LogLevel . Information ,
information : LogLevel . Information ,
warn : LogLevel . Warning ,
warning : LogLevel . Warning ,
error : LogLevel . Error ,
critical : LogLevel . Critical ,
none : LogLevel . None ,
} ;
function parseLogLevel ( name ) {
// Case-insensitive matching via lower-casing
// Yes, I know case-folding is a complicated problem in Unicode, but we only support
// the ASCII strings defined in LogLevelNameMapping anyway, so it's fine -anurse.
const mapping = LogLevelNameMapping [ name . toLowerCase ( ) ] ;
if ( typeof mapping !== "undefined" ) {
return mapping ;
}
else {
throw new Error ( ` Unknown log level: ${ name } ` ) ;
}
}
/** A builder for configuring {@link @microsoft/signalr.HubConnection} instances. */
class HubConnectionBuilder {
configureLogging ( logging ) {
Arg . isRequired ( logging , "logging" ) ;
if ( isLogger ( logging ) ) {
this . logger = logging ;
}
else if ( typeof logging === "string" ) {
const logLevel = parseLogLevel ( logging ) ;
this . logger = new ConsoleLogger ( logLevel ) ;
}
else {
this . logger = new ConsoleLogger ( logging ) ;
}
return this ;
}
withUrl ( url , transportTypeOrOptions ) {
Arg . isRequired ( url , "url" ) ;
Arg . isNotEmpty ( url , "url" ) ;
this . url = url ;
// Flow-typing knows where it's at. Since HttpTransportType is a number and IHttpConnectionOptions is guaranteed
// to be an object, we know (as does TypeScript) this comparison is all we need to figure out which overload was called.
if ( typeof transportTypeOrOptions === "object" ) {
this . httpConnectionOptions = { ... this . httpConnectionOptions , ... transportTypeOrOptions } ;
}
else {
this . httpConnectionOptions = {
... this . httpConnectionOptions ,
transport : transportTypeOrOptions ,
} ;
}
return this ;
}
/ * * C o n f i g u r e s t h e { @ l i n k @ m i c r o s o f t / s i g n a l r . H u b C o n n e c t i o n } t o u s e t h e s p e c i f i e d H u b P r o t o c o l .
*
* @ param { IHubProtocol } protocol The { @ link @ microsoft / signalr . IHubProtocol } implementation to use .
* /
withHubProtocol ( protocol ) {
Arg . isRequired ( protocol , "protocol" ) ;
this . protocol = protocol ;
return this ;
}
withAutomaticReconnect ( retryDelaysOrReconnectPolicy ) {
if ( this . reconnectPolicy ) {
throw new Error ( "A reconnectPolicy has already been set." ) ;
}
if ( ! retryDelaysOrReconnectPolicy ) {
this . reconnectPolicy = new DefaultReconnectPolicy ( ) ;
}
else if ( Array . isArray ( retryDelaysOrReconnectPolicy ) ) {
this . reconnectPolicy = new DefaultReconnectPolicy ( retryDelaysOrReconnectPolicy ) ;
}
else {
this . reconnectPolicy = retryDelaysOrReconnectPolicy ;
}
return this ;
}
/ * * C o n f i g u r e s { @ l i n k @ m i c r o s o f t / s i g n a l r . H u b C o n n e c t i o n . s e r v e r T i m e o u t I n M i l l i s e c o n d s } f o r t h e { @ l i n k @ m i c r o s o f t / s i g n a l r . H u b C o n n e c t i o n } .
*
* @ returns The { @ link @ microsoft / signalr . HubConnectionBuilder } instance , for chaining .
* /
withServerTimeout ( milliseconds ) {
Arg . isRequired ( milliseconds , "milliseconds" ) ;
this . _serverTimeoutInMilliseconds = milliseconds ;
return this ;
}
/ * * C o n f i g u r e s { @ l i n k @ m i c r o s o f t / s i g n a l r . H u b C o n n e c t i o n . k e e p A l i v e I n t e r v a l I n M i l l i s e c o n d s } f o r t h e { @ l i n k @ m i c r o s o f t / s i g n a l r . H u b C o n n e c t i o n } .
*
* @ returns The { @ link @ microsoft / signalr . HubConnectionBuilder } instance , for chaining .
* /
withKeepAliveInterval ( milliseconds ) {
Arg . isRequired ( milliseconds , "milliseconds" ) ;
this . _keepAliveIntervalInMilliseconds = milliseconds ;
return this ;
}
/ * * E n a b l e s a n d c o n f i g u r e s o p t i o n s f o r t h e S t a t e f u l R e c o n n e c t f e a t u r e .
*
* @ returns The { @ link @ microsoft / signalr . HubConnectionBuilder } instance , for chaining .
* /
withStatefulReconnect ( options ) {
if ( this . httpConnectionOptions === undefined ) {
this . httpConnectionOptions = { } ;
}
this . httpConnectionOptions . _useStatefulReconnect = true ;
this . _statefulReconnectBufferSize = options === null || options === void 0 ? void 0 : options . bufferSize ;
return this ;
}
/ * * C r e a t e s a { @ l i n k @ m i c r o s o f t / s i g n a l r . H u b C o n n e c t i o n } f r o m t h e c o n f i g u r a t i o n o p t i o n s s p e c i f i e d i n t h i s b u i l d e r .
*
* @ returns { HubConnection } The configured { @ link @ microsoft / signalr . HubConnection } .
* /
build ( ) {
// If httpConnectionOptions has a logger, use it. Otherwise, override it with the one
// provided to configureLogger
const httpConnectionOptions = this . httpConnectionOptions || { } ;
// If it's 'null', the user **explicitly** asked for null, don't mess with it.
if ( httpConnectionOptions . logger === undefined ) {
// If our logger is undefined or null, that's OK, the HttpConnection constructor will handle it.
httpConnectionOptions . logger = this . logger ;
}
// Now create the connection
if ( ! this . url ) {
throw new Error ( "The 'HubConnectionBuilder.withUrl' method must be called before building the connection." ) ;
}
const connection = new HttpConnection ( this . url , httpConnectionOptions ) ;
return HubConnection . create ( connection , this . logger || NullLogger . instance , this . protocol || new JsonHubProtocol ( ) , this . reconnectPolicy , this . _serverTimeoutInMilliseconds , this . _keepAliveIntervalInMilliseconds , this . _statefulReconnectBufferSize ) ;
}
}
function isLogger ( logger ) {
return logger . log !== undefined ;
}
; // CONCATENATED MODULE: ./src/index.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
2021-07-19 01:10:52 +00:00
2024-01-17 03:00:56 +00:00
; // CONCATENATED MODULE: ./src/browser-index.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// This is where we add any polyfills we'll need for the browser. It is the entry module for browser-specific builds.
// Copy from Array.prototype into Uint8Array to polyfill on IE. It's OK because the implementations of indexOf and slice use properties
// that exist on Uint8Array with the same name, and JavaScript is magic.
// We make them 'writable' because the Buffer polyfill messes with it as well.
if ( ! Uint8Array . prototype . indexOf ) {
Object . defineProperty ( Uint8Array . prototype , "indexOf" , {
value : Array . prototype . indexOf ,
writable : true ,
} ) ;
}
if ( ! Uint8Array . prototype . slice ) {
Object . defineProperty ( Uint8Array . prototype , "slice" , {
// wrap the slice in Uint8Array so it looks like a Uint8Array.slice call
// eslint-disable-next-line object-shorthand
value : function ( start , end ) { return new Uint8Array ( Array . prototype . slice . call ( this , start , end ) ) ; } ,
writable : true ,
} ) ;
}
if ( ! Uint8Array . prototype . forEach ) {
Object . defineProperty ( Uint8Array . prototype , "forEach" , {
value : Array . prototype . forEach ,
writable : true ,
} ) ;
}
/******/ return _ _webpack _exports _ _ ;
/******/ } ) ( )
;
2021-07-19 01:10:52 +00:00
} ) ;
//# sourceMappingURL=signalr.js.map