Skip to content

Commit

Permalink
Merge pull request #532 from illacloud/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
AruSeito authored Nov 21, 2022
2 parents 1fa9baf + 6cd82d7 commit deb99a5
Show file tree
Hide file tree
Showing 32 changed files with 581 additions and 382 deletions.
1 change: 1 addition & 0 deletions apps/builder/.env.cloud
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
VITE_API_BASE_URL=https://localhost/api/v1
VITE_SENTRY_SERVER_API=https://[email protected]/1
VITE_SENTRY_ENV=prod
VITE_INSTANCE_ID=CLOUD
NODE_ENV=production
174 changes: 174 additions & 0 deletions apps/builder/src/api/ws/illaWS.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { Callback, Signal, Target } from "@/api/ws/interface"
import {
ADD_DISPLAY_NAME,
DisplayNameGenerator,
REMOVE_DISPLAY_NAME,
UPDATE_DISPLAY_NAME,
} from "@/utils/generators/generateDisplayName"
import store from "@/store"
import { getLocalStorage } from "@/utils/storage"
import { getPayload } from "@/api/ws/index"
import { configActions } from "@/redux/config/configSlice"
import { getIsOnline } from "@/redux/config/configSelector"

const HEARTBEAT_PING_TIMEOUT = 2 * 1000
const HEARTBEAT_PONG_TIMEOUT = 5 * 1000
const RECONNECT_TIMEOUT = 5 * 1000
const REPEAT_LIMIT = 5

const pingMessage = JSON.stringify({
signal: 0,
option: 0,
target: 0,
payload: [],
broadcast: null,
})

export class ILLAWebsocket {
url: string
ws: WebSocket | null = null
repeat: number = 0
lockReconnect: boolean = false
forbidReconnect: boolean = false
pingTimeoutId: number = -1
pongTimeoutId: number = -1
isOnline: boolean = getIsOnline(store.getState())

constructor(url: string) {
this.url = url
this.createWebsocket()
}

private createWebsocket() {
try {
this.ws = new WebSocket(this.url)
this.initEventHandle()
} catch (e) {
this.reconnect()
throw e
}
}

private initEventHandle() {
if (this.ws) {
this.ws.onclose = () => {
this.reconnect()
}
this.ws.onerror = () => {
this.reconnect()
}
this.ws.onopen = () => {
console.log(`[WS OPENED](${this.url}) connection succeeded`)
store.dispatch(configActions.updateDevicesOnlineStatusReducer(true))
this.send(
getPayload(Signal.SIGNAL_ENTER, Target.TARGET_NOTHING, false, null, [
{
authToken: getLocalStorage("token"),
},
]),
)
this.isOnline = true
this.repeat = 0
this.heartCheck()
}
this.ws.onmessage = (event) => {
this.onMessage(event)
this.heartCheck()
}
}
}

private reconnect() {
if (this.forbidReconnect) return
if (this.isOnline) {
store.dispatch(configActions.updateDevicesOnlineStatusReducer(false))
this.isOnline = false
}
if (REPEAT_LIMIT <= this.repeat) return
if (this.lockReconnect) return
this.lockReconnect = true
this.repeat++
setTimeout(() => {
this.createWebsocket()
this.lockReconnect = false
}, RECONNECT_TIMEOUT)
}

private heartCheck() {
this.heartReset()
this.heartStart()
}

private heartStart() {
if (this.forbidReconnect) return
this.pingTimeoutId = setTimeout(() => {
this.ws?.send(pingMessage)
this.pongTimeoutId = setTimeout(() => {
if (this.isOnline) {
store.dispatch(configActions.updateDevicesOnlineStatusReducer(false))
this.isOnline = false
}
this.ws?.close()
}, HEARTBEAT_PONG_TIMEOUT)
}, HEARTBEAT_PING_TIMEOUT)
}

private heartReset() {
clearTimeout(this.pingTimeoutId)
clearTimeout(this.pongTimeoutId)
}
public close() {
this.forbidReconnect = true
this.heartReset()
this.ws?.close()
}
public onMessage(event: MessageEvent) {
const message = event.data
if (typeof message !== "string") {
return
}

const dataList = message.split("\n")
dataList.forEach((data: string) => {
let callback: Callback<unknown> = JSON.parse(data)
if (callback.errorCode === 0) {
if (callback.broadcast != null) {
let broadcast = callback.broadcast
let type = broadcast.type
let payload = broadcast.payload
switch (type) {
case `${ADD_DISPLAY_NAME}/remote`: {
;(payload as string[]).forEach((name) => {
DisplayNameGenerator.displayNameList.add(name)
})
break
}
case `${REMOVE_DISPLAY_NAME}/remote`: {
;(payload as string[]).forEach((name) => {
DisplayNameGenerator.displayNameList.delete(name)
})
break
}
case `${UPDATE_DISPLAY_NAME}/remote`: {
DisplayNameGenerator.displayNameList.delete(payload[0])
DisplayNameGenerator.displayNameList.add(payload[1])
break
}
default: {
try {
store.dispatch({
type,
payload,
})
} catch (ignore) {}
}
}
}
}
})
}

public send(message: string) {
this.ws?.send(message)
}
}
160 changes: 7 additions & 153 deletions apps/builder/src/api/ws/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import {
Broadcast,
Callback,
ILLAWebSocket,
ILLAWebSocketComponentPayload,
Room,
RoomType,
Signal,
Target,
} from "./interface"
import store from "@/store"
import { AxiosRequestConfig } from "axios"
import { Api } from "@/api/base"
import { getLocalStorage } from "@/utils/storage"
import {
ADD_DISPLAY_NAME,
DisplayNameGenerator,
REMOVE_DISPLAY_NAME,
UPDATE_DISPLAY_NAME,
} from "@/utils/generators/generateDisplayName"
import { ComponentNode } from "@/redux/currentApp/editor/components/componentsState"
import { ILLAWebsocket } from "@/api/ws/illaWS"

export function transformComponentReduxPayloadToWsPayload(
componentNodes: ComponentNode[] | ComponentNode,
Expand Down Expand Up @@ -63,7 +54,7 @@ export function getPayload<T>(
export const wsMap = new Map()

export class Connection {
static roomMap: Map<string, ILLAWebSocket> = new Map()
static roomMap: Map<string, ILLAWebsocket> = new Map()

static enterRoom(
type: RoomType,
Expand Down Expand Up @@ -93,7 +84,7 @@ export class Connection {
Api.request<Room>(
config,
(response) => {
let ws = generateWs(response.data.wsURL)
let ws = generateNewWs(response.data.wsURL)
this.roomMap.set(type + roomId, ws)
},
(error) => {},
Expand All @@ -103,7 +94,7 @@ export class Connection {
)
}

static getRoom(type: RoomType, roomId: string): WebSocket | undefined {
static getRoom(type: RoomType, roomId: string): ILLAWebsocket | undefined {
return this.roomMap.get(type + roomId)
}

Expand All @@ -113,148 +104,11 @@ export class Connection {
ws.send(
getPayload(Signal.SIGNAL_LEAVE, Target.TARGET_NOTHING, false, null, []),
)
ws.close(1000)
ws.close()
}
}
}

function onMessage(this: ILLAWebSocket, event: MessageEvent) {
resetHeartbeat(this)
const message = event.data
if (typeof message !== "string") {
return
}

const dataList = message.split("\n")
dataList.forEach((data: string) => {
let callback: Callback<unknown> = JSON.parse(data)
if (callback.errorCode === 0) {
if (callback.broadcast != null) {
let broadcast = callback.broadcast
let type = broadcast.type
let payload = broadcast.payload
switch (type) {
case `${ADD_DISPLAY_NAME}/remote`: {
;(payload as string[]).forEach((name) => {
DisplayNameGenerator.displayNameList.add(name)
})
break
}
case `${REMOVE_DISPLAY_NAME}/remote`: {
;(payload as string[]).forEach((name) => {
DisplayNameGenerator.displayNameList.delete(name)
})
break
}
case `${UPDATE_DISPLAY_NAME}/remote`: {
DisplayNameGenerator.displayNameList.delete(payload[0])
DisplayNameGenerator.displayNameList.add(payload[1])
break
}
default: {
try {
store.dispatch({
type,
payload,
})
} catch (ignore) {}
}
}
}
}
})
}

function onError(this: ILLAWebSocket, event: Event) {
console.error(`[WS ERROR](${this.url} is error)`, event)
this.close(4000, "close with error")
}

function onClose(this: ILLAWebSocket, event: CloseEvent) {
if (event.code !== 1000) {
reconnect(this)
} else {
clearWSTimeout(this)
wsMap.delete(this.url)
}
console.warn(`[WS CLOSED](${this.url}) ${event.code}:${event.reason}`)
}

function onOpen(this: ILLAWebSocket, event: Event) {
this.offline = false
startHeartbeat(this)
console.log(`[WS OPENED](${this.url}) connection succeeded`)
this.send(
getPayload(Signal.SIGNAL_ENTER, Target.TARGET_NOTHING, false, null, [
{
authToken: getLocalStorage("token"),
},
]),
)
}

const HEARTBEAT_TIMEOUT = 2 * 1000
const HEARTBEAT_SERVER_TIMEOUT = 5 * 1000
const RECONNECT_TIMEOUT = 5 * 1000

function clearWSTimeout(ws: ILLAWebSocket) {
ws.timeout && clearTimeout(ws.timeout)
ws.serverTimeout && clearTimeout(ws.serverTimeout)
ws.debounceTimeout && clearTimeout(ws.debounceTimeout)
}

const pingMessage = JSON.stringify({
signal: 0,
option: 0,
target: 0,
payload: [],
broadcast: null,
})

function startHeartbeat(ws: ILLAWebSocket) {
ws.timeout = setTimeout(() => {
ws.send(pingMessage)
ws.serverTimeout = setTimeout(() => {
ws.offline = true
// TODO:MESSAGE
ws.close(4001)
}, HEARTBEAT_SERVER_TIMEOUT)
}, HEARTBEAT_TIMEOUT)
}

function resetHeartbeat(ws: ILLAWebSocket) {
clearWSTimeout(ws)
startHeartbeat(ws)
}

function reconnect(ws: ILLAWebSocket) {
clearWSTimeout(ws)
const callNow = !ws.debounceTimeout
ws.debounceTimeout = setTimeout(() => {
ws.debounceTimeout = null
reconnect(ws)
}, RECONNECT_TIMEOUT)
if (callNow) {
generateWs(ws.url)
}
}

function initWsConfig(ws: ILLAWebSocket) {
ws.timeout = null
ws.serverTimeout = null
ws.debounceTimeout = wsMap.get(ws.url)
? wsMap.get(ws.url).debounceTimeout
: null
ws.offline = wsMap.get(ws.url) ? wsMap.get(ws.url).offline : false
wsMap.set(ws.url, ws)
}

export function generateWs(url: string) {
const ws: ILLAWebSocket = new WebSocket(url)
ws.onopen = onOpen
ws.onerror = onError
ws.onclose = onClose
ws.onmessage = onMessage
initWsConfig(ws)
return ws
export function generateNewWs(url: string) {
return new ILLAWebsocket(url)
}
Loading

0 comments on commit deb99a5

Please sign in to comment.