/
githubmirror
/
meteor
Обзор
Документация
Войти
/
githubmirror
/
meteor
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
devel
packages/socket-stream-client/browser.js
229 строк
7 KB
Italo José
Release 3.5.1 ⌛ (#14555)
06 авг 2026, 02:08
Не верифицирован
06 авг 2026, 02:08
efef960
Код
Авторство
О чём код?
import { toSockjsUrl, toWebsocketUrl, } from "./urls.js"; import { StreamClientCommon } from "./common.js"; // SockJS is imported statically to avoid the startup latency that dynamic // import() would introduce in _launchConnection(). When a non-SockJS transport // is selected, SockJS remains in the bundle but is never used — the connection // goes through native WebSocket directly. import SockJS from "./sockjs-1.6.1-min-.js"; export class ClientStream extends StreamClientCommon { // @param url {String} URL to Meteor app // "http://subdomain.meteor.com/" or "/" or // "ddp+sockjs://foo-**.meteor.com/sockjs" constructor(url, options) { super(options); this._initCommon(this.options); //// Constants // how long between hearing heartbeat from the server until we declare // the connection dead. heartbeats come every 45s (stream_server.js) // // NOTE: this is a older timeout mechanism. We now send heartbeats at // the DDP level (https://github.com/meteor/meteor/pull/1865), and // expect those timeouts to kill a non-responsive connection before // this timeout fires. This is kept around for compatibility (when // talking to a server that doesn't support DDP heartbeats) and can be // removed later. this.HEARTBEAT_TIMEOUT = 100 * 1000; this.rawUrl = url; this.socket = null; this.lastError = null; this.heartbeatTimer = null; // Listen to global 'online' event if we are running in a browser. window.addEventListener( 'online', this._online.bind(this), false /* useCapture */ ); //// Kickoff! this._launchConnection(); } // data is a utf8 string. Data sent while not connected is dropped on // the floor, and it is up the user of this API to retransmit lost // messages on 'reset' send(data) { if (this.currentStatus.connected) { this.socket.send(data); } } // Changes where this connection points _changeUrl(url) { this.rawUrl = url; } _connected() { if (this.connectionTimer) { clearTimeout(this.connectionTimer); this.connectionTimer = null; } if (this.currentStatus.connected) { // already connected. do nothing. this probably shouldn't happen. return; } // update status this.currentStatus.status = 'connected'; this.currentStatus.connected = true; this.currentStatus.retryCount = 0; this.statusChanged(); // fire resets. This must come after status change so that clients // can call send from within a reset callback. this.forEachCallback('reset', callback => { callback(); }); } _cleanup(maybeError) { this._clearConnectionAndHeartbeatTimers(); if (this.socket) { this.socket.onmessage = this.socket.onclose = this.socket.onerror = this.socket.onheartbeat = () => {}; this.socket.close(); this.socket = null; // Only fire disconnect callbacks when there was a socket to close, // matching the node implementation. _cleanup also runs at the top of // every (re)connection attempt, and firing here with no socket sent a // phantom 'disconnect' event to consumers once per retry cycle. this.forEachCallback('disconnect', callback => { callback(maybeError); }); } } _clearConnectionAndHeartbeatTimers() { if (this.connectionTimer) { clearTimeout(this.connectionTimer); this.connectionTimer = null; } if (this.heartbeatTimer) { clearTimeout(this.heartbeatTimer); this.heartbeatTimer = null; } } _heartbeat_timeout() { console.log('Connection timeout. No sockjs heartbeat received.'); this._lostConnection(new this.ConnectionError("Heartbeat timed out")); } _heartbeat_received() { // The watchdog exists to detect missing SockJS heartbeat frames, which // the server emits every 45s. Native WebSocket transports have no such // frames, so on a quiet connection (e.g. DDP heartbeats disabled) the // timer would be guaranteed to fire and kill a healthy connection. if (this._transport !== 'sockjs') return; // If we've already permanently shut down this stream, the timeout is // already cleared, and we don't need to set it again. if (this._forcedToDisconnect) return; if (this.heartbeatTimer) clearTimeout(this.heartbeatTimer); this.heartbeatTimer = setTimeout( this._heartbeat_timeout.bind(this), this.HEARTBEAT_TIMEOUT ); } _sockjsProtocolsWhitelist() { // only allow polling protocols. no streaming. streaming // makes safari spin. var protocolsWhitelist = [ 'xdr-polling', 'xhr-polling', 'iframe-xhr-polling', 'jsonp-polling' ]; // iOS 4 and 5 and below crash when using websockets over certain // proxies. this seems to be resolved with iOS 6. eg // https://github.com/LearnBoost/socket.io/issues/193#issuecomment-7308865. // // iOS <4 doesn't support websockets at all so sockjs will just // immediately fall back to http var noWebsockets = navigator && /iPhone|iPad|iPod/.test(navigator.userAgent) && /OS 4_|OS 5_/.test(navigator.userAgent); if (!noWebsockets) protocolsWhitelist = ['websocket'].concat(protocolsWhitelist); return protocolsWhitelist; } _launchConnection() { this._cleanup(); // cleanup the old socket, if there was one. const transport = __meteor_runtime_config__.DDP_TRANSPORT || 'sockjs'; this._transport = transport; if (transport === 'sockjs') { const options = { transports: this._sockjsProtocolsWhitelist(), ...this.options._sockjsOptions }; // Convert raw URL to SockJS URL each time we open a connection, so // that we can connect to random hostnames and get around browser // per-host connection limits. this.socket = new SockJS(toSockjsUrl(this.rawUrl), undefined, options); } else { // All non-SockJS transports use native WebSocket on the client. this.socket = new WebSocket(toWebsocketUrl(this.rawUrl)); } this.socket.onopen = data => { this.lastError = null; this._connected(); }; this.socket.onmessage = data => { this.lastError = null; this._heartbeat_received(); if (this.currentStatus.connected) { this.forEachCallback('message', callback => { callback(data.data); }); } }; this.socket.onclose = () => { this._lostConnection(); }; this.socket.onerror = error => { const { lastError } = this; this.lastError = error; if (lastError) return; console.error( 'stream error', error, new Date().toDateString() ); }; this.socket.onheartbeat = () => { this.lastError = null; this._heartbeat_received(); }; if (this.connectionTimer) clearTimeout(this.connectionTimer); this.connectionTimer = setTimeout(() => { this._lostConnection( new this.ConnectionError("DDP connection timed out") ); }, this.CONNECT_TIMEOUT); } }