UNPKG

@parlour/parlour-client

Version:

The parlour websocket client

374 lines (334 loc) 7.3 kB
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;