pusher-js
Version:
Pusher JavaScript library for browser, React Native, NodeJS and web workers
219 lines (189 loc) • 5.45 kB
text/typescript
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;