@parlour/parlour-client
Version:
The parlour websocket client
374 lines (334 loc) • 7.3 kB
JavaScript
import EventModel from './event-model';
import ServerEvents from './server-events';
function delay(timeout) {
timeout = timeout || 0;
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, timeout);
});
}
function getRoomChannel(roomId) {
return `room:${roomId}`;
}
function getUserChannel(userId) {
return `user:${userId}`;
}
function buildQueryString(queryStringsMap) {
let qsArr = [];
for(let key in queryStringsMap) {
qsArr.push(`${key}=${queryStringsMap[key]}`);
}
return qsArr.join('&');
}
class SocketClient extends EventModel {
constructor(data) {
super(data);
this.externalQueryString = this.externalQueryString || {};
this.ping = 0;
this.refMap = {};
this.rooms = [];
}
get userChannel() {
return getUserChannel(this.userId);
}
get isConnected() {
return this.socket && this.socket.readyState === WebSocket.OPEN;
}
get isConnecting() {
return this.socket && this.socket.readyState === WebSocket.CONNECTING;
}
get isClosing() {
return this.socket && this.socket.readyState === WebSocket.CLOSING;
}
get isClosed() {
return this.socket && this.socket.readyState === WebSocket.CLOSED;
}
_handle(data) {
const messageHandler = this.messageHandler;
const jsonData = JSON.parse(data);
const handler = messageHandler[jsonData.event] || messageHandler.default;
handler(this, jsonData.event, jsonData.topic, jsonData.ref, jsonData.payload);
}
connect() {
const Debug = this.Debug;
if(!this.socket || this.isClosed) {
const queryStrings = {
userId: this.userId,
userName: this.userName,
token: this.token,
...this.externalQueryString,
};
const queryStringText = buildQueryString(queryStrings);
const socket = this.socket = new WebSocket(`${this.url}?${queryStringText}`);
socket.onopen = (e) => {
// Debug.log('join room', this.room, this.userChannel);
this.refMap = {};
for(let i in this.rooms) {
let room = this.rooms[i];
this.join(room);
}
this.join(this.userChannel);
this.startHeartbeat();
Debug.log('[Socket] Open', e);
this.trigger('connect');
};
socket.onclose = (e) => {
this.stopHeartbeat();
Debug.log('[Socket] Close', e);
this.trigger('close', [e]);
if(this._isReconnectFlowEnabled) {
delete this._isReconnectFlowEnabled;
this.connect();
} else if(typeof this.reconnectPeriod === 'number') {
const client = this;
(async () => {
Debug.log('Reconnect auto...');
await delay(client.reconnectPeriod);
client.connect();
Debug.log('....done');
})();
}
};
socket.onmessage = (e) => {
Debug.log('[Socket] Message', e.data);
this._handle(e.data);
};
socket.onerror = (e) => {
Debug.log('[Socket] Error', e);
this.trigger('error', [e]);
};
}
}
end() {
if(this.socket) {
this.socket.close();
}
}
reconnect() {
this._isReconnectFlowEnabled = true;
this.end();
}
startHeartbeat() {
this.stopHeartbeat();
this.heartbeatTimer = setTimeout(async () => {
try {
if(this.isConnected) {
const resp = await this.heartbeat(this.userChannel);
const ping = Date.now() - resp.response.payload.t;
this.ping = ping;
this.trigger(ServerEvents.Ping, [this.ping]);
}
} catch(err) {
// failed to update ping value
} finally {
this.startHeartbeat();
}
}, this.heartbeatInterval);
}
stopHeartbeat() {
if(this.heartbeatTimer) {
clearTimeout(this.heartbeatTimer);
delete this.heartbeatTimer;
}
}
/* API。*/
responseRef(ref, payload) {
const handler = this.refMap[ref];
if(handler) {
delete this.refMap[ref];
handler.done(payload);
}
}
send({event, topic, payload}={}) {
const ref = this.refCounter.next().toString();
const data = {
event: event,
topic: topic,
payload: payload || '',
ref: ref,
};
if(event === ServerEvents.Join || event === ServerEvents.Leave) {
data.join_ref = this.refJoinCounter.next();
}
this.socket.send(JSON.stringify(data));
const refMap = this.refMap;
const refHandler = refMap[ref] = {};
return new Promise(resolve => {
refHandler.done = (payload) => {
resolve(payload);
};
});
}
join(topic, payload) {
return this.send({
event: ServerEvents.Join,
topic: topic,
payload: payload,
});
}
leave(topic, payload) {
return this.send({
event: ServerEvents.Leave,
topic: topic,
payload: payload,
});
}
heartbeat(topic) {
return this.send({
event: ServerEvents.Heartbeat,
topic: topic,
payload: {
t: Date.now()
},
});
}
broadcastToRoom(roomId, event, payload) {
return this.send({
event: ServerEvents.Broadcast,
topic: getRoomChannel(roomId),
payload: {
event: event,
payload: payload,
},
});
}
echo(topic, payload) {
return this.send({
event: ServerEvents.Echo,
topic: topic,
payload: payload,
});
}
presence(topic) {
return this.send({
event: ServerEvents.Presence,
topic: topic,
});
}
sendMessageTo(userId, event, payload) {
return this.send({
event: ServerEvents.Forward,
topic: this.userChannel,
payload: {
userId: getUserChannel(userId),
event: event,
payload: payload,
},
});
}
/*
* alias: ""
* name: ""
* password: ""
* subject: ""
* cache: {}
*/
createRoom(data) {
return this.send({
event: ServerEvents.CreateRoom,
topic: this.userChannel,
payload: {
info: data,
},
});
}
dismissRoom(roomId) {
return this.send({
event: ServerEvents.DismissRoom,
topic: this.userChannel,
payload: {
id: roomId,
},
});
}
roomByAlias(alias) {
return this.send({
event: ServerEvents.RoomByAlias,
topic: this.userChannel,
payload: {
alias: alias,
},
});
}
getRooms() {
return this.send({
event: ServerEvents.Rooms,
topic: this.userChannel,
payload: {}
});
}
getRoomsByOwner() {
return this.send({
event: ServerEvents.RoomsByOwner,
topic: this.userChannel,
payload: {}
});
}
/*
* queryData: { subject: "" }
*/
searchRooms(queryData) {
return this.send({
event: ServerEvents.SearchRooms,
topic: this.userChannel,
payload: queryData,
});
}
joinRoom(id, password) {
const index = this.rooms.indexOf(id);
if(index < 0) {
this.rooms.push(id);
}
return this.join(getRoomChannel(id), {
password: password || ''
});
}
joinLobby() {
return this.joinRoom('lobby');
}
leaveRoom(id) {
const index = this.rooms.indexOf(id);
if(index >= 0) {
this.rooms.splice(index, 1);
}
return this.leave(getRoomChannel(id));
}
leaveLobby() {
return this.leaveRoom('lobby');
}
getCache(roomId) {
return this.send({
event: ServerEvents.Cache,
topic: getRoomChannel(roomId),
payload: {},
});
}
addCache(roomId, key, value) {
return this.send({
event: ServerEvents.CacheAdd,
topic: getRoomChannel(roomId),
payload: {
key,
value
},
});
}
removeCache(roomId, key) {
return this.send({
event: ServerEvents.CacheRemove,
topic: getRoomChannel(roomId),
payload: {
key
},
});
}
getServerTime() {
return this.send({
event: ServerEvents.Timestamp,
topic: this.userChannel,
payload: {}
});
}
}
export default SocketClient;