@js-toolkit/web-utils 1.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EventEmitterListener.d.ts +58 -0
- package/EventEmitterListener.js +1 -0
- package/EventEmitterListener.utils.d.ts +30 -0
- package/EventEmitterListener.utils.js +1 -0
- package/EventListeners.d.ts +8 -0
- package/EventListeners.js +1 -0
- package/FullscreenController.d.ts +61 -0
- package/FullscreenController.js +1 -0
- package/LICENSE +21 -0
- package/PipController.d.ts +31 -0
- package/PipController.js +1 -0
- package/README.md +5 -0
- package/base64ToDataUrl.d.ts +2 -0
- package/base64ToDataUrl.js +1 -0
- package/blobToDataUrl.d.ts +2 -0
- package/blobToDataUrl.js +1 -0
- package/copyToClipboard.d.ts +1 -0
- package/copyToClipboard.js +1 -0
- package/createLoop.d.ts +8 -0
- package/createLoop.js +1 -0
- package/createRafLoop.d.ts +10 -0
- package/createRafLoop.js +1 -0
- package/dataUrlToBlob.d.ts +2 -0
- package/dataUrlToBlob.js +1 -0
- package/detection/getIOSVersion.d.ts +9 -0
- package/detection/getIOSVersion.js +1 -0
- package/detection/getUAParserResult.d.ts +2 -0
- package/detection/getUAParserResult.js +1 -0
- package/detection/isAirPlayAvailable.d.ts +2 -0
- package/detection/isAirPlayAvailable.js +1 -0
- package/detection/isAndroid.d.ts +2 -0
- package/detection/isAndroid.js +1 -0
- package/detection/isIOS.d.ts +2 -0
- package/detection/isIOS.js +1 -0
- package/detection/isMacOS.d.ts +2 -0
- package/detection/isMacOS.js +1 -0
- package/detection/isMobile.d.ts +2 -0
- package/detection/isMobile.js +1 -0
- package/detection/isSafari.d.ts +2 -0
- package/detection/isSafari.js +1 -0
- package/fullscreen.d.ts +29 -0
- package/fullscreen.js +1 -0
- package/ga/DataLayerProxy.d.ts +8 -0
- package/ga/DataLayerProxy.js +1 -0
- package/ga/getHandler.d.ts +19 -0
- package/ga/getHandler.js +1 -0
- package/ga/iframeMessenger.d.ts +7 -0
- package/ga/iframeMessenger.js +1 -0
- package/ga/types.d.ts +41 -0
- package/ga/types.js +1 -0
- package/getAppContainer.d.ts +2 -0
- package/getAppContainer.js +1 -0
- package/getAspectRatio.d.ts +11 -0
- package/getAspectRatio.js +1 -0
- package/getBrowserLanguage.d.ts +7 -0
- package/getBrowserLanguage.js +1 -0
- package/getEventAwaiter.d.ts +6 -0
- package/getEventAwaiter.js +1 -0
- package/getGeoCoordinates.d.ts +2 -0
- package/getGeoCoordinates.js +1 -0
- package/getGeoLocality.d.ts +5 -0
- package/getGeoLocality.js +1 -0
- package/getInnerRect.d.ts +6 -0
- package/getInnerRect.js +1 -0
- package/getInnerXDimensions.d.ts +7 -0
- package/getInnerXDimensions.js +1 -0
- package/getInnerYDimensions.d.ts +7 -0
- package/getInnerYDimensions.js +1 -0
- package/getRandomID.d.ts +2 -0
- package/getRandomID.js +1 -0
- package/getScreenSize.d.ts +6 -0
- package/getScreenSize.js +1 -0
- package/getSecondsCounter.d.ts +23 -0
- package/getSecondsCounter.js +1 -0
- package/iframe/getAutoConnectClient.d.ts +15 -0
- package/iframe/getAutoConnectClient.js +1 -0
- package/iframe/getAutoConnectHost.d.ts +21 -0
- package/iframe/getAutoConnectHost.js +1 -0
- package/iframe/getAutoConnector.d.ts +32 -0
- package/iframe/getAutoConnector.js +1 -0
- package/iframe/getOriginFromMessage.d.ts +1 -0
- package/iframe/getOriginFromMessage.js +1 -0
- package/iframe/isIframeLoaded.d.ts +5 -0
- package/iframe/isIframeLoaded.js +1 -0
- package/iframe/messages.d.ts +19 -0
- package/iframe/messages.js +1 -0
- package/iframe/utils.d.ts +3 -0
- package/iframe/utils.js +1 -0
- package/imageToBlob.d.ts +2 -0
- package/imageToBlob.js +1 -0
- package/isImageTypeSupported.d.ts +1 -0
- package/isImageTypeSupported.js +1 -0
- package/isStandaloneApp.d.ts +2 -0
- package/isStandaloneApp.js +1 -0
- package/isWebPSupported.d.ts +1 -0
- package/isWebPSupported.js +1 -0
- package/loadImage.d.ts +3 -0
- package/loadImage.js +1 -0
- package/loadScript.d.ts +4 -0
- package/loadScript.js +1 -0
- package/media/MediaStreamController.d.ts +15 -0
- package/media/MediaStreamController.js +1 -0
- package/media/getDurationTime.d.ts +1 -0
- package/media/getDurationTime.js +1 -0
- package/media/isMediaSeekable.d.ts +1 -0
- package/media/isMediaSeekable.js +1 -0
- package/media/mse.d.ts +8 -0
- package/media/mse.js +1 -0
- package/media/resetMedia.d.ts +2 -0
- package/media/resetMedia.js +1 -0
- package/media/timeRanges.d.ts +3 -0
- package/media/timeRanges.js +1 -0
- package/media/toggleNativeSubtitles.d.ts +6 -0
- package/media/toggleNativeSubtitles.js +1 -0
- package/onDOMReady.d.ts +3 -0
- package/onDOMReady.js +1 -0
- package/onPageReady.d.ts +3 -0
- package/onPageReady.js +1 -0
- package/package.json +46 -0
- package/performance/getNavigationTiming.d.ts +1 -0
- package/performance/getNavigationTiming.js +1 -0
- package/preventDefault.d.ts +5 -0
- package/preventDefault.js +1 -0
- package/rafCallback.d.ts +5 -0
- package/rafCallback.js +1 -0
- package/saveFileAs.d.ts +2 -0
- package/saveFileAs.js +1 -0
- package/serviceWorker.d.ts +8 -0
- package/serviceWorker.js +1 -0
- package/stopPropagation.d.ts +4 -0
- package/stopPropagation.js +1 -0
- package/takeSnapshot.d.ts +9 -0
- package/takeSnapshot.js +1 -0
- package/toBase64.d.ts +2 -0
- package/toBase64.js +1 -0
- package/toLocalPoint.d.ts +5 -0
- package/toLocalPoint.js +1 -0
- package/types/index.d.ts +4 -0
- package/types/index.js +1 -0
- package/types/refs.d.ts +1 -0
- package/types/refs.js +1 -0
- package/webrtc/PeerConnection.d.ts +68 -0
- package/webrtc/PeerConnection.js +1 -0
- package/webrtc/sdputils.d.ts +20 -0
- package/webrtc/sdputils.js +1 -0
- package/ws/WSController.d.ts +50 -0
- package/ws/WSController.js +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{__awaiter}from"tslib";import DataEventEmitter,{}from"@jstoolkit/utils/DataEventEmitter";import getErrorMessage from"@jstoolkit/utils/getErrorMessage";import{hasIn}from"@jstoolkit/utils/hasIn";import*as sdpUtils from"./sdputils";export class PeerConnection extends DataEventEmitter{get Events(){return PeerConnection.Events}constructor(e={}){var t;super(),this.options=e,this.logger=null!==(t=e.logger)&&void 0!==t?t:console,this.pc=this.createPC()}clear(){this.pc.onsignalingstatechange=null,this.pc.onconnectionstatechange=null,this.pc.oniceconnectionstatechange=null,this.pc.ontrack=null,this.pc.onnegotiationneeded=null,this.pc.onicecandidate=null,this.pc.onicecandidateerror=null}createPC(){const e=new RTCPeerConnection(this.options.rtc);return e.onsignalingstatechange=()=>{this.logger.debug(`Signaling state changed to: ${e.signalingState}`),"closed"===e.signalingState&&(this.emit(this.Events.Closed),this.clear())},hasIn(RTCPeerConnection.prototype,"onconnectionstatechange")?e.onconnectionstatechange=()=>{this.logger.debug(`Connection state changed to: ${e.connectionState}`),"connected"===e.connectionState?this.emit(this.Events.Connected):"disconnected"===e.connectionState&&this.emit(this.Events.Disconnected)}:e.oniceconnectionstatechange=()=>{this.logger.debug(`ICE connection state changed to: ${e.iceConnectionState}`),"connected"===e.iceConnectionState?this.emit(this.Events.Connected):"disconnected"===e.iceConnectionState&&this.emit(this.Events.Disconnected)},e.ontrack=({streams:e,track:t})=>{if(this.logger.debug("Remote stream received.",e.length,t.kind),0===e.length)return;const[n]=e;n.onremovetrack=()=>{this.logger.debug("onremovetrack"),this.emit(this.Events.RemoteStreamChanged,n)},n.onaddtrack=()=>{this.logger.debug("onaddtrack"),this.emit(this.Events.RemoteStreamChanged,n)},this.emit(this.Events.RemoteStreamChanged,n)},e.onnegotiationneeded=()=>{const{iceConnectionState:t}=e;"connected"!==t&&"completed"!==t||(this.logger.debug("Reinitializing connection..."),this.emit(this.Events.ReinitializingConnectionRequired))},e.onicecandidate=({candidate:e})=>{e?sdpUtils.isValidIceCandidate(e)&&this.emit(this.Events.LocalIceCandidate,e):this.emit(this.Events.EndOfIceCandidates)},e.onicecandidateerror=e=>{if(e instanceof RTCPeerConnectionIceErrorEvent){const{errorCode:t,errorText:n,hostCandidate:i,url:o}=e;this.logger.warn(`ICE candidate error: errorCode=${t}, errorText=${n}, hostCandidate=${i}, url=${o}`)}else this.logger.warn(`ICE candidate error: ${getErrorMessage(e)}`)},e}setLocalDescription(e,t){return __awaiter(this,void 0,void 0,(function*(){const n=sdpUtils.prepareLocalDescription(t,this.options.codecs);return yield e.setLocalDescription(n),n}))}setRemoteDescription(e,t){return __awaiter(this,void 0,void 0,(function*(){const n=sdpUtils.prepareRemoteDescription(t,this.options.codecs);return yield e.setRemoteDescription(n),n}))}isConnected(){return"connected"===this.pc.iceConnectionState||"completed"===this.pc.iceConnectionState}isClosed(){return"closed"===this.pc.signalingState}addIceCandidate(e){return this.pc.addIceCandidate(e)}attachStream(e){const t=this.pc.getSenders(),n=t.length?e.getTracks().filter((e=>!t.find((({track:t})=>!!t&&t.id===e.id)))):e.getTracks();n.length?(n.forEach((t=>this.pc.addTrack(t,e))),this.logger.debug(`Attached ${n.length} track(s) to a peer connection.`)):this.logger.debug("No tracks to attach to a peer connection.")}reattachStream(e){const t=this.pc.iceConnectionState;if("new"!==t&&"connected"!==t&&"completed"!==t)return;const n=e?e.getTracks():[];this.pc.getSenders().forEach((e=>{const{track:t}=e;!t||"ended"!==t.readyState&&n.find((e=>e.id===t.id))||(this.pc.removeTrack(e),this.logger.debug(`'${t&&t.kind}' track is removed from a peer connection.`))})),e&&this.attachStream(e)}createOffer(){return __awaiter(this,void 0,void 0,(function*(){const e=yield this.pc.createOffer(this.options.offerOptions);return this.setLocalDescription(this.pc,e)}))}createAnswer(e){return __awaiter(this,void 0,void 0,(function*(){yield this.setRemoteDescription(this.pc,e);const t=yield this.pc.createAnswer(this.options.offerOptions);return this.setLocalDescription(this.pc,t)}))}applyAnswer(e){return this.setRemoteDescription(this.pc,e)}reconnect(){this.close(),this.pc=this.createPC()}close(){this.pc.close(),this.pc.onsignalingstatechange&&(this.emit(this.Events.Closed),this.clear())}destroy(){this.close(),this.removeAllListeners()}}!function(e){let t;!function(e){e.LocalIceCandidate="LocalIceCandidate",e.EndOfIceCandidates="EndOfIceCandidates",e.Connected="Connected",e.Disconnected="Disconnected",e.RemoteStreamChanged="RemoteStreamChanged",e.ReinitializingConnectionRequired="ReinitializingConnectionRequired",e.Closed="Closed"}(t=e.Events||(e.Events={}))}(PeerConnection||(PeerConnection={}));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface PreferCodecs {
|
|
2
|
+
videoRecvCodec?: string;
|
|
3
|
+
videoSendCodec?: string;
|
|
4
|
+
audioRecvCodec?: string;
|
|
5
|
+
audioSendCodec?: string;
|
|
6
|
+
opusStereo?: string;
|
|
7
|
+
opusFec?: string;
|
|
8
|
+
opusDtx?: string;
|
|
9
|
+
opusMaxPbr?: string;
|
|
10
|
+
videoSendInitialBitrate?: string;
|
|
11
|
+
videoSendBitrate?: string;
|
|
12
|
+
videoRecvBitrate?: string;
|
|
13
|
+
audioSendBitrate?: string;
|
|
14
|
+
audioRecvBitrate?: string;
|
|
15
|
+
videoFec?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function trace(text: string): void;
|
|
18
|
+
export declare function isValidIceCandidate({ candidate }: RTCIceCandidate, config?: RTCConfiguration): boolean;
|
|
19
|
+
export declare function prepareLocalDescription(desc: RTCSessionDescriptionInit, preferCodecs?: PreferCodecs): RTCSessionDescriptionInit;
|
|
20
|
+
export declare function prepareRemoteDescription(desc: RTCSessionDescriptionInit, preferCodecs?: PreferCodecs): RTCSessionDescriptionInit;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function trace(e){if(e.endsWith("\n")&&(e=e.substring(0,e.length-1)),window.performance){const t=(window.performance.now()/1e3).toFixed(3);console.log(`${t}: ${e}`)}else console.log(e)}function iceCandidateType(e){return e.split(" ")[7]}export function isValidIceCandidate({candidate:e},t){return!(!e||e.includes("tcp"))&&(!t||"relay"!==t.iceTransportPolicy||"relay"===iceCandidateType(e))}function maybeSetOpusOptions(e,t){return"true"===t.opusStereo?e=setCodecParam(e,"opus/48000","stereo","1"):"false"===t.opusStereo&&(e=removeCodecParam(e,"opus/48000","stereo")),"true"===t.opusFec?e=setCodecParam(e,"opus/48000","useinbandfec","1"):"false"===t.opusFec&&(e=removeCodecParam(e,"opus/48000","useinbandfec")),"true"===t.opusDtx?e=setCodecParam(e,"opus/48000","usedtx","1"):"false"===t.opusDtx&&(e=removeCodecParam(e,"opus/48000","usedtx")),t.opusMaxPbr&&(e=setCodecParam(e,"opus/48000","maxplaybackrate",t.opusMaxPbr)),e}function maybeSetAudioSendBitRate(e,t){return t.audioSendBitrate?(trace(`Prefer audio send bitrate: ${t.audioSendBitrate}`),preferBitRate(e,t.audioSendBitrate,"audio")):e}function maybeSetAudioReceiveBitRate(e,t){return t.audioRecvBitrate?(trace(`Prefer audio receive bitrate: ${t.audioRecvBitrate}`),preferBitRate(e,t.audioRecvBitrate,"audio")):e}function maybeSetVideoSendBitRate(e,t){return t.videoSendBitrate?(trace(`Prefer video send bitrate: ${t.videoSendBitrate}`),preferBitRate(e,t.videoSendBitrate,"video")):e}function maybeSetVideoReceiveBitRate(e,t){return t.videoRecvBitrate?(trace(`Prefer video receive bitrate: ${t.videoRecvBitrate}`),preferBitRate(e,t.videoRecvBitrate,"video")):e}function preferBitRate(e,t,n){const i=e.split("\r\n"),o=findLine(i,"m=",n);if(-1===o)return trace("Failed to add bandwidth line to sdp, as no m-line found"),e;let r=findLineInRange(i,o+1,-1,"m=");-1===r&&(r=i.length);const a=findLineInRange(i,o+1,r,"c=");if(-1===a)return trace("Failed to add bandwidth line to sdp, as no c-line found"),e;const d=findLineInRange(i,a+1,r,"b=AS");d>=0&&i.splice(d,1);const c=`b=AS:${t}`;return i.splice(a+1,0,c),e=i.join("\r\n")}function maybeSetVideoSendInitialBitRate(e,t){let n=t.videoSendInitialBitrate;if(!n)return e;let i=n;const o=t.videoSendBitrate;o&&(n>o&&(trace(`Clamping initial bitrate to max bitrate of ${o} kbps.`),n=o,t.videoSendInitialBitrate=n),i=o);if(-1===findLine(e.split("\r\n"),"m=","video"))return trace("Failed to find video m-line"),e;const r=t.videoRecvCodec||"";return e=setCodecParam(e,r,"x-google-min-bitrate",t.videoSendInitialBitrate||""),e=setCodecParam(e,r,"x-google-max-bitrate",i.toString())}function removePayloadTypeFromMline(e,t){const n=e.split(" ");for(let e=0;e<n.length;++e)n[e]===t.toString()&&n.splice(e,1);return n.join(" ")}function removeCodecByName(e,t){const n=findLine(e,"a=rtpmap",t);if(-1===n)return e;const i=getCodecPayloadTypeFromLine(e[n]);e.splice(n,1);const o=findLine(e,"m=","video");return-1===o||(e[o]=removePayloadTypeFromMline(e[o],i)),e}function removeCodecByPayloadType(e,t){const n=findLine(e,"a=rtpmap",t.toString());if(-1===n)return e;e.splice(n,1);const i=findLine(e,"m=","video");return-1===i||(e[i]=removePayloadTypeFromMline(e[i],t)),e}function maybeRemoveVideoFec(e,t){if("false"!==t.videoFec)return e;let n=e.split("\r\n"),i=findLine(n,"a=rtpmap","red");if(-1===i)return e;const o=getCodecPayloadTypeFromLine(n[i]);if(n=removeCodecByPayloadType(n,o),n=removeCodecByName(n,"ulpfec"),i=findLine(n,"a=fmtp",o.toString()),-1===i)return e;const r=parseFmtpLine(n[i]).pt;return null==r?e:(n.splice(i,1),n=removeCodecByPayloadType(n,r),n.join("\r\n"))}function maybePreferAudioSendCodec(e,t){return maybePreferCodec(e,"audio","send",t.audioSendCodec)}function maybePreferAudioReceiveCodec(e,t){return maybePreferCodec(e,"audio","receive",t.audioRecvCodec)}function maybePreferVideoSendCodec(e,t){return maybePreferCodec(e,"video","send",t.videoSendCodec)}function maybePreferVideoReceiveCodec(e,t){return maybePreferCodec(e,"video","receive",t.videoRecvCodec)}function maybePreferCodec(e,t,n,i){if(!i)return e;const o=e.split("\r\n"),r=findLine(o,"m=",t);if(-1===r)return e;const a=getCodecPayloadType(o,i);return a&&(o[r]=setDefaultCodec(o[r],a)),e=o.join("\r\n")}function setCodecParam(e,t,n,i){const o=e.split("\r\n"),r=findFmtpLine(o,t);if(null===r){const r=findLine(o,"a=rtpmap",t);if(-1===r)return e;const a={pt:getCodecPayloadTypeFromLine(o[r]).toString(),params:{[n]:i}};o.splice(r+1,0,writeFmtpLine(a))}else{const e=parseFmtpLine(o[r]);e.params[n]=i,o[r]=writeFmtpLine(e)}return e=o.join("\r\n")}function removeCodecParam(e,t,n){const i=e.split("\r\n"),o=findFmtpLine(i,t);if(null===o)return e;const r=parseFmtpLine(i[o]);if(r){delete r.params[n];const e=writeFmtpLine(r);null===e?i.splice(o,1):i[o]=e}return e=i.join("\r\n")}function parseFmtpLine(e){const t={},n=e.indexOf(" "),i=e.substring(n+1).split("; "),o=/a=fmtp:(\d+)/.exec(e);o&&2===o.length&&(t.pt=o[1]);const r={};return i.forEach((e=>{const t=e.split("=");2===t.length&&(r[t[0]]=t[1])})),t.params=r,t}function writeFmtpLine(e){if(!e.hasOwnProperty("pt")||!e.hasOwnProperty("params"))return"";const{pt:t="",params:n={}}=e,i=Object.entries(n).map((([e,t])=>`${e}=${t}`));return 0===i.length?"":`a=fmtp:${t.toString()} ${i.join("; ")}`}function findFmtpLine(e,t){const n=getCodecPayloadType(e,t);return n?findLine(e,`a=fmtp:${n.toString()}`):null}function findLine(e,t,n){return findLineInRange(e,0,-1,t,n)}function findLineInRange(e,t,n,i,o){const r=-1!==n?n:e.length;for(let n=t;n<r;++n)if(e[n].startsWith(i)&&(!o||e[n].toLowerCase().includes(o.toLowerCase())))return n;return-1}function getCodecPayloadType(e,t){const n=findLine(e,"a=rtpmap",t);return n>=0?getCodecPayloadTypeFromLine(e[n]):null}function getCodecPayloadTypeFromLine(e){const t=/a=rtpmap:(\d+) [a-zA-Z0-9-]+\/\d+/.exec(e);return t&&2===t.length?t[1]:""}function setDefaultCodec(e,t){const n=e.split(" "),i=n.slice(0,3);i.push(t);for(let e=3;e<n.length;e++)n[e]!==t&&i.push(n[e]);return i.join(" ")}export function prepareLocalDescription(e,t={}){let n=maybePreferAudioReceiveCodec(e.sdp||"",t);n=maybePreferVideoReceiveCodec(n,t),n=maybeSetAudioReceiveBitRate(n,t),n=maybeSetVideoReceiveBitRate(n,t),n=maybeRemoveVideoFec(n,t);return{sdp:n,type:e.type}}export function prepareRemoteDescription(e,t={}){let n=maybeSetOpusOptions(e.sdp||"",t);n=maybePreferAudioSendCodec(n,t),n=maybePreferVideoSendCodec(n,t),n=maybeSetAudioSendBitRate(n,t),n=maybeSetVideoSendBitRate(n,t),n=maybeSetVideoSendInitialBitRate(n,t),n=maybeRemoveVideoFec(n,t);return{sdp:n,type:e.type}}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import ReconnectingWebSocket, { type ErrorEvent, type Message, type Options as BaseOptions, type UrlProvider } from 'reconnecting-websocket';
|
|
2
|
+
import DataEventEmitter, { type DataEventListener, type DataEventMap } from '@jstoolkit/utils/DataEventEmitter';
|
|
3
|
+
export declare class WSController<TData = unknown> extends DataEventEmitter<WSController.EventMap<TData>, WSController<TData>> {
|
|
4
|
+
get Events(): typeof WSController.Events;
|
|
5
|
+
readonly logger: NonNullable<WSController.Options['logger']>;
|
|
6
|
+
protected readonly ws: ReconnectingWebSocket;
|
|
7
|
+
private readonly listener;
|
|
8
|
+
private readonly reconnectOnIdle;
|
|
9
|
+
private readonly halfOpen;
|
|
10
|
+
constructor(url: UrlProvider, options?: WSController.Options | undefined);
|
|
11
|
+
isConnected(): boolean;
|
|
12
|
+
getUrl(): string;
|
|
13
|
+
connect(): void;
|
|
14
|
+
send(data: Message): void;
|
|
15
|
+
close(code?: number | undefined, reason?: string | undefined): void;
|
|
16
|
+
destroy(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare namespace WSController {
|
|
19
|
+
interface Options extends OptionalToUndefined<BaseOptions & Partial<Pick<WebSocket, 'binaryType'>>> {
|
|
20
|
+
readonly protocols?: ConstructorParameters<typeof ReconnectingWebSocket>['1'] | undefined;
|
|
21
|
+
/** Reconnecting after an idle timeout (no message events). Default 30_000. */
|
|
22
|
+
readonly idleTimeout?: number | undefined;
|
|
23
|
+
readonly logger?: Pick<Console, 'warn' | 'info'> | undefined;
|
|
24
|
+
readonly halfOpenDetection?: {
|
|
25
|
+
/** Delay should be equal to the interval at which your server sends out pings plus a conservative assumption of the latency. */
|
|
26
|
+
readonly pingTimeout?: number;
|
|
27
|
+
readonly outMessage?: Message | (() => Message);
|
|
28
|
+
/** Returns `true` if inMessage is ping message. */
|
|
29
|
+
readonly inMessageFilter?: (message: unknown) => boolean;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
enum Events {
|
|
33
|
+
Connected = "Connected",
|
|
34
|
+
Message = "Message",
|
|
35
|
+
Error = "Error",
|
|
36
|
+
/** If connection was closed and the object will not to try to reconnect */
|
|
37
|
+
Closed = "Closed"
|
|
38
|
+
}
|
|
39
|
+
type EventMap<TData = unknown> = DataEventMap<DefineAll<Events, {
|
|
40
|
+
[Events.Connected]: [];
|
|
41
|
+
[Events.Message]: [
|
|
42
|
+
event: OmitStrict<MessageEvent<TData>, keyof Event | 'initMessageEvent'>
|
|
43
|
+
];
|
|
44
|
+
[Events.Error]: [event: Pick<ErrorEvent, 'error' | 'message'>];
|
|
45
|
+
[Events.Closed]: [{
|
|
46
|
+
readonly reason: string;
|
|
47
|
+
} | undefined];
|
|
48
|
+
}>, WSController<TData>>;
|
|
49
|
+
type EventHandler<T extends Events = Events, TData = unknown> = DataEventListener<EventMap<TData>, T, WSController<TData>>;
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{__rest}from"tslib";import ReconnectingWebSocket,{}from"reconnecting-websocket";import DataEventEmitter,{}from"@jstoolkit/utils/DataEventEmitter";import delayed from"@jstoolkit/utils/delayed";import{EventEmitterListener}from"../EventEmitterListener";function getNotConnectedError(){return new Error("The object is not connected yet.")}export class WSController extends DataEventEmitter{get Events(){return WSController.Events}constructor(e,t){super();const n=null!=t?t:{},{logger:s,protocols:o,binaryType:i,idleTimeout:r,halfOpenDetection:c,startClosed:l}=n,a=__rest(n,["logger","protocols","binaryType","idleTimeout","halfOpenDetection","startClosed"]);this.logger=null!=s?s:console,this.ws=new ReconnectingWebSocket(e,o,Object.assign(Object.assign({},a),{startClosed:!0})),null!=i&&(this.ws.binaryType=i),this.listener=new EventEmitterListener(this.ws),r&&r>0&&(this.reconnectOnIdle=delayed((()=>{this.logger.info(`WS connection reconnecting after ${r}ms due to inactivity.`),this.ws.reconnect()}),r)),this.halfOpen=(()=>{if(!c)return;const{pingTimeout:e=12e3,outMessage:t=(()=>JSON.stringify({type:"pong"})),inMessageFilter:n=(e=>e&&"object"==typeof e&&"ping"===e.type)}=c,s=delayed((()=>{this.logger.info(`WS connection reconnecting after ${e}ms due to the probability of a half-open connection.`),this.ws.reconnect()}),e),o=()=>{s(),this.ws.send("function"!=typeof t||t instanceof Blob?t:t())};return{heartbeat:o,cancel:()=>s.cancel(),onMessage:({data:e})=>{n(e)?o():s()}}})(),this.listener.on("open",(()=>{var e;this.reconnectOnIdle&&this.reconnectOnIdle(),null===(e=this.halfOpen)||void 0===e||e.heartbeat(),this.emit(this.Events.Connected)})).on("close",(({code:e})=>{var n,s;null===(n=this.reconnectOnIdle)||void 0===n||n.cancel(),null===(s=this.halfOpen)||void 0===s||s.cancel(),null!=(null==t?void 0:t.maxRetries)&&this.ws.readyState===this.ws.CLOSED&&this.ws.retryCount===t.maxRetries&&this.close(e,`Failed to connect after ${t.maxRetries} attempts.`)})).on("message",(e=>{var t;this.reconnectOnIdle&&this.reconnectOnIdle(),null===(t=this.halfOpen)||void 0===t||t.onMessage(e),this.emit(this.Events.Message,e)})).on("error",(({error:e,message:t})=>{this.emit(this.Events.Error,{error:e,message:t})})),this.halfOpen&&(this.isConnected()||this.ws.readyState===this.ws.CONNECTING)&&this.halfOpen.heartbeat(),l||this.connect()}isConnected(){return this.ws.readyState===this.ws.OPEN}getUrl(){return this.ws.url}connect(){this.ws.reconnect()}send(e){if(!this.ws)throw getNotConnectedError();this.ws.send(e)}close(e,t){const n=!!this.ws;try{this.reconnectOnIdle&&this.reconnectOnIdle.cancel(),this.halfOpen&&this.halfOpen.cancel(),this.ws.close(e,t)}catch(e){this.logger.warn(e)}n&&this.emit(this.Events.Closed,t?{reason:t}:void 0)}destroy(){this.close(),this.listener.removeAllListeners(),this.removeAllListeners()}}!function(e){let t;!function(e){e.Connected="Connected",e.Message="Message",e.Error="Error",e.Closed="Closed"}(t=e.Events||(e.Events={}))}(WSController||(WSController={}));
|