UNPKG

pusher-js

Version:

Pusher JavaScript library for browser, React Native, NodeJS and web workers

219 lines (189 loc) 5.45 kB
import URLLocation from "./url_location"; import State from "./state"; import Socket from "../socket"; import SocketHooks from "./socket_hooks"; import Util from "../util"; import Ajax from "./ajax"; import HTTPRequest from "./http_request"; import Runtime from "runtime"; var autoIncrement = 1; class HTTPSocket implements Socket { hooks: SocketHooks; session: string; location: URLLocation; readyState: State; stream: HTTPRequest; onopen:() => void; onerror:(error : any) => void; onclose:(closeEvent : any) => void; onmessage:(message : any) => void; onactivity:() => void; constructor(hooks : SocketHooks, url : string) { this.hooks = hooks; this.session = randomNumber(1000) + "/" + randomString(8); this.location = getLocation(url); this.readyState = State.CONNECTING; this.openStream(); } send(payload : any) { return this.sendRaw(JSON.stringify([payload])); } ping(){ this.hooks.sendHeartbeat(this); } close(code : any, reason : any) { this.onClose(code, reason, true); } /** For internal use only */ sendRaw(payload : any) : boolean { if (this.readyState === State.OPEN) { try { Runtime.createSocketRequest( "POST", getUniqueURL(getSendURL(this.location, this.session)) ).start(payload); return true; } catch(e) { return false; } } else { return false; } } /** For internal use only */ reconnect() { this.closeStream(); this.openStream(); }; /** For internal use only */ onClose(code, reason, wasClean) { this.closeStream(); this.readyState = State.CLOSED; if (this.onclose) { this.onclose({ code: code, reason: reason, wasClean: wasClean }); } } private onChunk(chunk) { if (chunk.status !== 200) { return; } if (this.readyState === State.OPEN) { this.onActivity(); } var payload; var type = chunk.data.slice(0, 1); switch(type) { case 'o': payload = JSON.parse(chunk.data.slice(1) || '{}'); this.onOpen(payload); break; case 'a': payload = JSON.parse(chunk.data.slice(1) || '[]'); for (var i = 0; i < payload.length; i++){ this.onEvent(payload[i]); } break; case 'm': payload = JSON.parse(chunk.data.slice(1) || 'null'); this.onEvent(payload); break; case 'h': this.hooks.onHeartbeat(this); break; case 'c': payload = JSON.parse(chunk.data.slice(1) || '[]'); this.onClose(payload[0], payload[1], true); break; } } private onOpen(options) { if (this.readyState === State.CONNECTING) { if (options && options.hostname) { this.location.base = replaceHost(this.location.base, options.hostname); } this.readyState = State.OPEN; if (this.onopen) { this.onopen(); } } else { this.onClose(1006, "Server lost session", true); } } private onEvent(event) { if (this.readyState === State.OPEN && this.onmessage) { this.onmessage({ data: event }); } } private onActivity() { if (this.onactivity) { this.onactivity(); } } private onError(error) { if (this.onerror) { this.onerror(error); } } private openStream() { this.stream = Runtime.createSocketRequest( "POST", getUniqueURL(this.hooks.getReceiveURL(this.location, this.session)) ); this.stream.bind("chunk", (chunk)=> { this.onChunk(chunk); }); this.stream.bind("finished", (status)=> { this.hooks.onFinished(this, status); }); this.stream.bind("buffer_too_long", ()=> { this.reconnect(); }); try { this.stream.start(); } catch (error) { Util.defer(()=> { this.onError(error); this.onClose(1006, "Could not start streaming", false); }); } } private closeStream() { if (this.stream) { this.stream.unbind_all(); this.stream.close(); this.stream = null; } } } function getLocation(url) : URLLocation { var parts = /([^\?]*)\/*(\??.*)/.exec(url); return { base: parts[1], queryString: parts[2] }; } function getSendURL(url : URLLocation, session : string) : string { return url.base + "/" + session + "/xhr_send"; } function getUniqueURL(url : string) : string { var separator = (url.indexOf('?') === -1) ? "?" : "&"; return url + separator + "t=" + (+new Date()) + "&n=" + autoIncrement++; } function replaceHost(url : string, hostname : string) : string { var urlParts = /(https?:\/\/)([^\/:]+)((\/|:)?.*)/.exec(url); return urlParts[1] + hostname + urlParts[3]; } function randomNumber(max : number) : number { return Math.floor(Math.random() * max); } function randomString(length : number) : string { var result = []; for (var i = 0; i < length; i++) { result.push(randomNumber(32).toString(32)); } return result.join(''); } export default HTTPSocket;